00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053 package require Tcl 8.2
00054 package require log ;
00055
00056 namespace ::ftp {
00057 namespace export DisplayMsg Open Close Cd Pwd Type List NList \
00058 FileSize ModTime Delete Rename Put Append Get Reget \
00059 Newer Quote MkDir RmDir
00060
00061 serial = 0
00062 VERBOSE = 0
00063 DEBUG = 0
00064 }
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083 ret ::ftp::DisplayMsg (type s , type msg , optional state ="") {
00084
00085 upvar ::ftp::ftp$s ftp
00086
00087 if { ([info exists ftp(Output)]) && ($ftp(Output) != "") } {
00088 eval [concat $ftp(Output) {$s $msg $state}]
00089 return
00090 }
00091
00092 # FIX #476729. Instead of changing the documentation this
00093 # procedure is changed to enforce the documented
00094 # behaviour. IOW, this procedure will not throw
00095 # errors anymore. At the same time printing to stdout
00096 # is exchanged against calls into the 'log' module
00097 # tcllib, which is much easier to customize for the
00098 # needs of any application using the ftp module. The
00099 # variable VERBOSE is still relevant as it controls
00100 # whether this procedure is called or not.
00101
00102 global errorInfo
00103 switch -exact -- $state {
00104 data {log::log debug "$state | $msg"}
00105 control {log::log debug "$state | $msg"}
00106 error {log::log error "$state | E: $msg:\n$errorInfo"}
00107 default {log::log debug "$state | $msg"}
00108 }
00109 return
00110 }
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121 ret ::ftp::Timeout (type s) {
00122 upvar ::ftp::ftp$s ftp
00123
00124 after cancel $ftp(Wait)
00125 set ftp(state.control) 1
00126
00127 DisplayMsg "" "Timeout of control connection after $ftp(Timeout) sec.!" error
00128 Command $ftp(Command) timeout
00129 return
00130 }
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147 ret ::ftp::WaitOrTimeout (type s) {
00148 upvar ::ftp::ftp$s ftp
00149
00150 set retvar 1
00151
00152 if { ![string length $ftp(Command)] && [info exists ftp(state.control)] } {
00153
00154 set ftp(Wait) [after [expr {$ftp(Timeout) * 1000}] [list [namespace current]::Timeout $s]]
00155
00156 vwait ::ftp::ftp${s}(state.control)
00157 set retvar $ftp(state.control)
00158 }
00159
00160 if {$ftp(Error) != ""} {
00161 set errmsg $ftp(Error)
00162 set ftp(Error) ""
00163 DisplayMsg $s $errmsg error
00164 }
00165
00166 return $retvar
00167 }
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182 ret ::ftp::WaitComplete (type s , type value) {
00183 upvar ::ftp::ftp$s ftp
00184
00185 if {![info exists ftp(Command)]} {
00186 set ftp(state.control) $value
00187 return $value
00188 }
00189 if { ![string length $ftp(Command)] && [info exists ftp(state.data)] } {
00190 vwait ::ftp::ftp${s}(state.data)
00191 }
00192
00193 catch {after cancel $ftp(Wait)}
00194 set ftp(state.control) $value
00195 return $ftp(state.control)
00196 }
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209 ret ::ftp::PutsCtrlSock (type s , optional command ="") {
00210 upvar ::ftp::ftp$s ftp
00211 variable DEBUG
00212
00213 if { $DEBUG } {
00214 DisplayMsg $s "---> $command"
00215 }
00216
00217 puts $ftp(CtrlSock) $command
00218 flush $ftp(CtrlSock)
00219 return
00220 }
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235 ret ::ftp::StateHandler (type s , optional sock ="") {
00236 upvar ::ftp::ftp$s ftp
00237 variable DEBUG
00238 variable VERBOSE
00239
00240 # disable fileevent on control socket, enable it at the and of the state machine
00241 # fileevent $ftp(CtrlSock) readable {}
00242
00243 # there is no socket (and no channel to get) if called from a procedure
00244
00245 set rc " "
00246 set msgtext {}
00247
00248 if { $sock != "" } {
00249
00250 set number 0 ;# Error condition
00251 catch {set number [gets $sock bufline]}
00252
00253 if { $number > 0 } {
00254
00255 # get return code, check for multi-line text
00256
00257 if {![regexp -- "^-?(\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line msgtext]} {
00258 set errmsg "C: Internal Error @ line 255.\
00259 Regex pattern not matching the input \"$bufline\""
00260 if {$VERBOSE} {
00261 DisplayMsg $s $errmsg control
00262 }
00263 } else {
00264 # multi-line format detected ("-"), get all the lines
00265 # until the real return code
00266
00267 set buffer $bufline
00268
00269 while { [string equal $multi_line "-"] } {
00270 set number [gets $sock bufline]
00271 if { $number > 0 } {
00272 append buffer \n "$bufline"
00273 regexp -- "(^\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line
00274 # multi_line is not set if the bufline does not match the regexp,
00275 # I.e. this keeps the '-' which started this around until the
00276 # closing line does match and sets it to space.
00277 }
00278 }
00279
00280 # Export the accumulated response. [Bug 1191607].
00281 set msgtext $buffer
00282 }
00283 } elseif { [eof $ftp(CtrlSock)] } {
00284 # remote server has closed control connection. kill
00285 # control socket, unset State to disable all following
00286 # commands. Killing the socket is done before
00287 # 'WaitComplete' to prevent it from recursively entering
00288 # this code, overflowing the stack (socket still existing,
00289 # still readable, still eof). [SF Tcllib Bug 15822535].
00290
00291 set rc 421
00292 catch {close $ftp(CtrlSock)}
00293 catch {unset ftp(CtrlSock)}
00294 catch {unset ftp(state.data)}
00295 if { $VERBOSE } {
00296 DisplayMsg $s "C: 421 Service not available, closing control connection." control
00297 }
00298 if {![string equal $ftp(State) "quit_sent"]} {
00299 set ftp(Error) "Service not available!"
00300 }
00301 CloseDataConn $s
00302 WaitComplete $s 0
00303 Command $ftp(Command) terminated
00304 catch {unset ftp(State)}
00305 return
00306 } else {
00307 # Fix SF bug #466746: Incomplete line, do nothing.
00308 return
00309 }
00310 }
00311
00312 if { $DEBUG } {
00313 DisplayMsg $s "-> rc=\"$rc\"\n-> msgtext=\"$msgtext\"\n-> state=\"$ftp(State)\""
00314 }
00315
00316 # In asynchronous mode, should we move on to the next state?
00317 set nextState 0
00318
00319 # system status replay
00320 if { [string equal $rc "211"] } {
00321 return
00322 }
00323
00324 # use only the first digit
00325 regexp -- "^\[0-9\]?" $rc rc
00326
00327 switch -exact -- $ftp(State) {
00328 user {
00329 switch -exact -- $rc {
00330 2 {
00331 PutsCtrlSock $s "USER $ftp(User)"
00332 set ftp(State) passwd
00333 Command $ftp(Command) user
00334 }
00335 default {
00336 set errmsg "Error connecting! $msgtext"
00337 set complete_with 0
00338 Command $ftp(Command) error $errmsg
00339 }
00340 }
00341 }
00342 passwd {
00343 switch -exact -- $rc {
00344 2 {
00345 set complete_with 1
00346 Command $ftp(Command) password
00347 }
00348 3 {
00349 PutsCtrlSock $s "PASS $ftp(Passwd)"
00350 set ftp(State) connect
00351 Command $ftp(Command) password
00352 }
00353 default {
00354 set errmsg "Error connecting! $msgtext"
00355 set complete_with 0
00356 Command $ftp(Command) error $msgtext
00357 }
00358 }
00359 }
00360 connect {
00361 switch -exact -- $rc {
00362 2 {
00363 # The type is set after this, and we want to report
00364 # that the connection is complete once the type is done
00365 set nextState 1
00366 if {[info exists ftp(NextState)] && ![llength $ftp(NextState)]} {
00367 Command $ftp(Command) connect $s
00368 } else {
00369 set complete_with 1
00370 }
00371 }
00372 default {
00373 set errmsg "Error connecting! $msgtext"
00374 set complete_with 0
00375 Command $ftp(Command) error $msgtext
00376 }
00377 }
00378 }
00379 connect_last {
00380 Command $ftp(Command) connect $s
00381 set complete_with 1
00382 }
00383 quit {
00384 PutsCtrlSock $s "QUIT"
00385 set ftp(State) quit_sent
00386 }
00387 quit_sent {
00388 switch -exact -- $rc {
00389 2 {
00390 set complete_with 1
00391 set nextState 1
00392 Command $ftp(Command) quit
00393 }
00394 default {
00395 set errmsg "Error disconnecting! $msgtext"
00396 set complete_with 0
00397 Command $ftp(Command) error $msgtext
00398 }
00399 }
00400 }
00401 quote {
00402 PutsCtrlSock $s $ftp(Cmd)
00403 set ftp(State) quote_sent
00404 }
00405 quote_sent {
00406 set complete_with 1
00407 set ftp(Quote) $buffer
00408 set nextState 1
00409 Command $ftp(Command) quote $buffer
00410 }
00411 type {
00412 if { [string equal $ftp(Type) "ascii"] } {
00413 PutsCtrlSock $s "TYPE A"
00414 } elseif { [string equal $ftp(Type) "binary"] } {
00415 PutsCtrlSock $s "TYPE I"
00416 } else {
00417 PutsCtrlSock $s "TYPE L"
00418 }
00419 set ftp(State) type_sent
00420 }
00421 type_sent {
00422 switch -exact -- $rc {
00423 2 {
00424 set complete_with 1
00425 set nextState 1
00426 Command $ftp(Command) type $ftp(Type)
00427 }
00428 default {
00429 set errmsg "Error setting type \"$ftp(Type)\"!"
00430 set complete_with 0
00431 Command $ftp(Command) error "error setting type \"$ftp(Type)\""
00432 }
00433 }
00434 }
00435 type_change {
00436 set ftp(Type) $ftp(type:changeto)
00437 set ftp(State) type
00438 StateHandler $s
00439 }
00440 nlist_active {
00441 if { [OpenActiveConn $s] } {
00442 PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
00443 set ftp(State) nlist_open
00444 } else {
00445 set errmsg "Error setting port!"
00446 }
00447 }
00448 nlist_passive {
00449 PutsCtrlSock $s "PASV"
00450 set ftp(State) nlist_open
00451 }
00452 nlist_open {
00453 switch -exact -- $rc {
00454 1 {}
00455 2 {
00456 if { [string equal $ftp(Mode) "passive"] } {
00457 if { ![OpenPassiveConn $s $buffer] } {
00458 set errmsg "Error setting PASSIVE mode!"
00459 set complete_with 0
00460 Command $ftp(Command) error "error setting passive mode"
00461 }
00462 }
00463 PutsCtrlSock $s "NLST$ftp(Dir)"
00464 set ftp(State) list_sent
00465 }
00466 default {
00467 if { [string equal $ftp(Mode) "passive"] } {
00468 set errmsg "Error setting PASSIVE mode!"
00469 } else {
00470 set errmsg "Error setting port!"
00471 }
00472 set complete_with 0
00473 Command $ftp(Command) error $errmsg
00474 }
00475 }
00476 }
00477 list_active {
00478 if { [OpenActiveConn $s] } {
00479 PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
00480 set ftp(State) list_open
00481 } else {
00482 set errmsg "Error setting port!"
00483 Command $ftp(Command) error $errmsg
00484 }
00485 }
00486 list_passive {
00487 PutsCtrlSock $s "PASV"
00488 set ftp(State) list_open
00489 }
00490 list_open {
00491 switch -exact -- $rc {
00492 1 {}
00493 2 {
00494 if { [string equal $ftp(Mode) "passive"] } {
00495 if { ![OpenPassiveConn $s $buffer] } {
00496 set errmsg "Error setting PASSIVE mode!"
00497 set complete_with 0
00498 Command $ftp(Command) error $errmsg
00499 }
00500 }
00501 PutsCtrlSock $s "LIST$ftp(Dir)"
00502 set ftp(State) list_sent
00503 }
00504 default {
00505 if { [string equal $ftp(Mode) "passive"] } {
00506 set errmsg "Error setting PASSIVE mode!"
00507 } else {
00508 set errmsg "Error setting port!"
00509 }
00510 set complete_with 0
00511 Command $ftp(Command) error $errmsg
00512 }
00513 }
00514 }
00515 list_sent {
00516 switch -exact -- $rc {
00517 1 -
00518 2 {
00519 set ftp(State) list_close
00520 }
00521 default {
00522 if { [string equal $ftp(Mode) "passive"] } {
00523 catch {unset ftp(state.data)}
00524 }
00525 set errmsg "Error getting directory listing!"
00526 set complete_with 0
00527 Command $ftp(Command) error $errmsg
00528 }
00529 }
00530 }
00531 list_close {
00532 switch -exact -- $rc {
00533 1 {}
00534 2 {
00535 set nextState 1
00536 if {[info exists ftp(NextState)] && ![llength $ftp(NextState)]} {
00537 Command $ftp(Command) list [ListPostProcess $ftp(List)]
00538 } else {
00539 set complete_with 1
00540 }
00541 }
00542 default {
00543 set errmsg "Error receiving list!"
00544 set complete_with 0
00545 Command $ftp(Command) error $errmsg
00546 }
00547 }
00548 }
00549 list_last {
00550 Command $ftp(Command) list [ListPostProcess $ftp(List)]
00551 set complete_with 1
00552 }
00553 size {
00554 PutsCtrlSock $s "SIZE $ftp(File)"
00555 set ftp(State) size_sent
00556 }
00557 size_sent {
00558 switch -exact -- $rc {
00559 2 {
00560 regexp -- "^\[0-9\]+ (.*)$" $buffer all ftp(FileSize)
00561 set complete_with 1
00562 set nextState 1
00563 Command $ftp(Command) size $ftp(File) $ftp(FileSize)
00564 }
00565 default {
00566 set errmsg "Error getting file size!"
00567 set complete_with 0
00568 Command $ftp(Command) error $errmsg
00569 }
00570 }
00571 }
00572 modtime {
00573 if {$ftp(DateTime) != ""} {
00574 PutsCtrlSock $s "MDTM $ftp(DateTime) $ftp(File)"
00575 } else { ;# No DateTime Specified
00576 PutsCtrlSock $s "MDTM $ftp(File)"
00577 }
00578 set ftp(State) modtime_sent
00579 }
00580 modtime_sent {
00581 switch -exact -- $rc {
00582 2 {
00583 regexp -- "^\[0-9\]+ (.*)$" $buffer all ftp(DateTime)
00584 set complete_with 1
00585 set nextState 1
00586 Command $ftp(Command) modtime $ftp(File) [ModTimePostProcess $ftp(DateTime)]
00587 }
00588 default {
00589 if {$ftp(DateTime) != ""} {
00590 set errmsg "Error setting modification time! No server MDTM support?"
00591 } else {
00592 set errmsg "Error getting modification time!"
00593 }
00594 set complete_with 0
00595 Command $ftp(Command) error $errmsg
00596 }
00597 }
00598 }
00599 pwd {
00600 PutsCtrlSock $s "PWD"
00601 set ftp(State) pwd_sent
00602 }
00603 pwd_sent {
00604 switch -exact -- $rc {
00605 2 {
00606 regexp -- "^.*\"(.*)\"" $buffer temp ftp(Dir)
00607 set complete_with 1
00608 set nextState 1
00609 Command $ftp(Command) pwd $ftp(Dir)
00610 }
00611 default {
00612 set errmsg "Error getting working dir!"
00613 set complete_with 0
00614 Command $ftp(Command) error $errmsg
00615 }
00616 }
00617 }
00618 cd {
00619 PutsCtrlSock $s "CWD$ftp(Dir)"
00620 set ftp(State) cd_sent
00621 }
00622 cd_sent {
00623 switch -exact -- $rc {
00624 1 {}
00625 2 {
00626 set complete_with 1
00627 set nextState 1
00628 Command $ftp(Command) cd $ftp(Dir)
00629 }
00630 default {
00631 set errmsg "Error changing directory to \"$ftp(Dir)\""
00632 set complete_with 0
00633 Command $ftp(Command) error $errmsg
00634 }
00635 }
00636 }
00637 mkdir {
00638 PutsCtrlSock $s "MKD $ftp(Dir)"
00639 set ftp(State) mkdir_sent
00640 }
00641 mkdir_sent {
00642 switch -exact -- $rc {
00643 2 {
00644 set complete_with 1
00645 set nextState 1
00646 Command $ftp(Command) mkdir $ftp(Dir)
00647 }
00648 default {
00649 set errmsg "Error making dir \"$ftp(Dir)\"!"
00650 set complete_with 0
00651 Command $ftp(Command) error $errmsg
00652 }
00653 }
00654 }
00655 rmdir {
00656 PutsCtrlSock $s "RMD $ftp(Dir)"
00657 set ftp(State) rmdir_sent
00658 }
00659 rmdir_sent {
00660 switch -exact -- $rc {
00661 2 {
00662 set complete_with 1
00663 set nextState 1
00664 Command $ftp(Command) rmdir $ftp(Dir)
00665 }
00666 default {
00667 set errmsg "Error removing directory!"
00668 set complete_with 0
00669 Command $ftp(Command) error $errmsg
00670 }
00671 }
00672 }
00673 delete {
00674 PutsCtrlSock $s "DELE $ftp(File)"
00675 set ftp(State) delete_sent
00676 }
00677 delete_sent {
00678 switch -exact -- $rc {
00679 2 {
00680 set complete_with 1
00681 set nextState 1
00682 Command $ftp(Command) delete $ftp(File)
00683 }
00684 default {
00685 set errmsg "Error deleting file \"$ftp(File)\"!"
00686 set complete_with 0
00687 Command $ftp(Command) error $errmsg
00688 }
00689 }
00690 }
00691 rename {
00692 PutsCtrlSock $s "RNFR $ftp(RenameFrom)"
00693 set ftp(State) rename_to
00694 }
00695 rename_to {
00696 switch -exact -- $rc {
00697 3 {
00698 PutsCtrlSock $s "RNTO $ftp(RenameTo)"
00699 set ftp(State) rename_sent
00700 }
00701 default {
00702 set errmsg "Error renaming file \"$ftp(RenameFrom)\"!"
00703 set complete_with 0
00704 Command $ftp(Command) error $errmsg
00705 }
00706 }
00707 }
00708 rename_sent {
00709 switch -exact -- $rc {
00710 2 {
00711 set complete_with 1
00712 set nextState 1
00713 Command $ftp(Command) rename $ftp(RenameFrom) $ftp(RenameTo)
00714 }
00715 default {
00716 set errmsg "Error renaming file \"$ftp(RenameFrom)\"!"
00717 set complete_with 0
00718 Command $ftp(Command) error $errmsg
00719 }
00720 }
00721 }
00722 put_active {
00723 if { [OpenActiveConn $s] } {
00724 PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
00725 set ftp(State) put_open
00726 } else {
00727 set errmsg "Error setting port!"
00728 Command $ftp(Command) error $errmsg
00729 }
00730 }
00731 put_passive {
00732 PutsCtrlSock $s "PASV"
00733 set ftp(State) put_open
00734 }
00735 put_open {
00736 switch -exact -- $rc {
00737 1 -
00738 2 {
00739 if { [string equal $ftp(Mode) "passive"] } {
00740 if { ![OpenPassiveConn $s $buffer] } {
00741 set errmsg "Error setting PASSIVE mode!"
00742 set complete_with 0
00743 Command $ftp(Command) error $errmsg
00744 }
00745 }
00746 PutsCtrlSock $s "STOR $ftp(RemoteFilename)"
00747 set ftp(State) put_sent
00748 }
00749 default {
00750 if { [string equal $ftp(Mode) "passive"] } {
00751 set errmsg "Error setting PASSIVE mode!"
00752 } else {
00753 set errmsg "Error setting port!"
00754 }
00755 set complete_with 0
00756 Command $ftp(Command) error $errmsg
00757 }
00758 }
00759 }
00760 put_sent {
00761 switch -exact -- $rc {
00762 1 -
00763 2 {
00764 set ftp(State) put_close
00765 }
00766 default {
00767 if { [string equal $ftp(Mode) "passive"] } {
00768 # close already opened DataConnection
00769 catch {unset ftp(state.data)}
00770 }
00771 set errmsg "Error opening connection!"
00772 set complete_with 0
00773 Command $ftp(Command) error $errmsg
00774 }
00775 }
00776 }
00777 put_close {
00778 switch -exact -- $rc {
00779 1 {
00780 # Keep going
00781 return
00782 }
00783 2 {
00784 set complete_with 1
00785 set nextState 1
00786 Command $ftp(Command) put $ftp(RemoteFilename)
00787 }
00788 default {
00789 DisplayMsg $s "rc = $rc msgtext = \"$msgtext\""
00790 set errmsg "Error storing file \"$ftp(RemoteFilename)\" due to \"$msgtext\""
00791 set complete_with 0
00792 Command $ftp(Command) error $errmsg
00793 }
00794 }
00795 }
00796 append_active {
00797 if { [OpenActiveConn $s] } {
00798 PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
00799 set ftp(State) append_open
00800 } else {
00801 set errmsg "Error setting port!"
00802 Command $ftp(Command) error $errmsg
00803 }
00804 }
00805 append_passive {
00806 PutsCtrlSock $s "PASV"
00807 set ftp(State) append_open
00808 }
00809 append_open {
00810 switch -exact -- $rc {
00811 1 -
00812 2 {
00813 if { [string equal $ftp(Mode) "passive"] } {
00814 if { ![OpenPassiveConn $s $buffer] } {
00815 set errmsg "Error setting PASSIVE mode!"
00816 set complete_with 0
00817 Command $ftp(Command) error $errmsg
00818 }
00819 }
00820 PutsCtrlSock $s "APPE $ftp(RemoteFilename)"
00821 set ftp(State) append_sent
00822 }
00823 default {
00824 if { [string equal $ftp(Mode) "passive"] } {
00825 set errmsg "Error setting PASSIVE mode!"
00826 } else {
00827 set errmsg "Error setting port!"
00828 }
00829 set complete_with 0
00830 Command $ftp(Command) error $errmsg
00831 }
00832 }
00833 }
00834 append_sent {
00835 switch -exact -- $rc {
00836 1 {
00837 set ftp(State) append_close
00838 }
00839 default {
00840 if { [string equal $ftp(Mode) "passive"] } {
00841 # close already opened DataConnection
00842 catch {unset ftp(state.data)}
00843 }
00844 set errmsg "Error opening connection!"
00845 set complete_with 0
00846 Command $ftp(Command) error $errmsg
00847 }
00848 }
00849 }
00850 append_close {
00851 switch -exact -- $rc {
00852 2 {
00853 set complete_with 1
00854 set nextState 1
00855 Command $ftp(Command) append $ftp(RemoteFilename)
00856 }
00857 default {
00858 set errmsg "Error storing file \"$ftp(RemoteFilename)\"!"
00859 set complete_with 0
00860 Command $ftp(Command) error $errmsg
00861 }
00862 }
00863 }
00864 reget_active {
00865 if { [OpenActiveConn $s] } {
00866 PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
00867 set ftp(State) reget_restart
00868 } else {
00869 set errmsg "Error setting port!"
00870 Command $ftp(Command) error $errmsg
00871 }
00872 }
00873 reget_passive {
00874 PutsCtrlSock $s "PASV"
00875 set ftp(State) reget_restart
00876 }
00877 reget_restart {
00878 switch -exact -- $rc {
00879 2 {
00880 if { [string equal $ftp(Mode) "passive"] } {
00881 if { ![OpenPassiveConn $s $buffer] } {
00882 set errmsg "Error setting PASSIVE mode!"
00883 set complete_with 0
00884 Command $ftp(Command) error $errmsg
00885 }
00886 }
00887 if { $ftp(FileSize) != 0 } {
00888 PutsCtrlSock $s "REST $ftp(FileSize)"
00889 set ftp(State) reget_open
00890 } else {
00891 PutsCtrlSock $s "RETR $ftp(RemoteFilename)"
00892 set ftp(State) reget_sent
00893 }
00894 }
00895 default {
00896 set errmsg "Error restarting filetransfer of \"$ftp(RemoteFilename)\"!"
00897 set complete_with 0
00898 Command $ftp(Command) error $errmsg
00899 }
00900 }
00901 }
00902 reget_open {
00903 switch -exact -- $rc {
00904 2 -
00905 3 {
00906 PutsCtrlSock $s "RETR $ftp(RemoteFilename)"
00907 set ftp(State) reget_sent
00908 }
00909 default {
00910 if { [string equal $ftp(Mode) "passive"] } {
00911 set errmsg "Error setting PASSIVE mode!"
00912 } else {
00913 set errmsg "Error setting port!"
00914 }
00915 set complete_with 0
00916 Command $ftp(Command) error $errmsg
00917 }
00918 }
00919 }
00920 reget_sent {
00921 switch -exact -- $rc {
00922 1 {
00923 set ftp(State) reget_close
00924 }
00925 default {
00926 if { [string equal $ftp(Mode) "passive"] } {
00927 # close already opened DataConnection
00928 catch {unset ftp(state.data)}
00929 }
00930 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
00931 set complete_with 0
00932 Command $ftp(Command) error $errmsg
00933 }
00934 }
00935 }
00936 reget_close {
00937 switch -exact -- $rc {
00938 2 {
00939 set complete_with 1
00940 set nextState 1
00941 Command $ftp(Command) get $ftp(RemoteFilename):$ftp(From):$ftp(To)
00942 unset ftp(From) ftp(To)
00943 }
00944 default {
00945 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
00946 set complete_with 0
00947 Command $ftp(Command) error $errmsg
00948 }
00949 }
00950 }
00951 get_active {
00952 if { [OpenActiveConn $s] } {
00953 PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
00954 set ftp(State) get_open
00955 } else {
00956 set errmsg "Error setting port!"
00957 Command $ftp(Command) error $errmsg
00958 }
00959 }
00960 get_passive {
00961 PutsCtrlSock $s "PASV"
00962 set ftp(State) get_open
00963 }
00964 get_open {
00965 switch -exact -- $rc {
00966 1 -
00967 2 -
00968 3 {
00969 if { [string equal $ftp(Mode) "passive"] } {
00970 if { ![OpenPassiveConn $s $buffer] } {
00971 set errmsg "Error setting PASSIVE mode!"
00972 set complete_with 0
00973 Command $ftp(Command) error $errmsg
00974 }
00975 }
00976 PutsCtrlSock $s "RETR $ftp(RemoteFilename)"
00977 set ftp(State) get_sent
00978 }
00979 default {
00980 if { [string equal $ftp(Mode) "passive"] } {
00981 set errmsg "Error setting PASSIVE mode!"
00982 } else {
00983 set errmsg "Error setting port!"
00984 }
00985 set complete_with 0
00986 Command $ftp(Command) error $errmsg
00987 }
00988 }
00989 }
00990 get_sent {
00991 switch -exact -- $rc {
00992 1 {
00993 set ftp(State) get_close
00994 }
00995 default {
00996 if { [string equal $ftp(Mode) "passive"] } {
00997 # close already opened DataConnection
00998 catch {unset ftp(state.data)}
00999 }
01000 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
01001 set complete_with 0
01002 Command $ftp(Command) error $errmsg
01003 }
01004 }
01005 }
01006 get_close {
01007 switch -exact -- $rc {
01008 2 {
01009 set complete_with 1
01010 set nextState 1
01011 if {$ftp(inline)} {
01012 upvar #0 $ftp(get:varname) returnData
01013 set returnData $ftp(GetData)
01014 Command $ftp(Command) get $ftp(GetData)
01015 } else {
01016 Command $ftp(Command) get $ftp(RemoteFilename)
01017 }
01018 }
01019 default {
01020 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
01021 set complete_with 0
01022 Command $ftp(Command) error $errmsg
01023 }
01024 }
01025 }
01026 default {
01027 error "Unknown state \"$ftp(State)\""
01028 }
01029 }
01030
01031 # finish waiting
01032 if { [info exists complete_with] } {
01033 WaitComplete $s $complete_with
01034 }
01035
01036 # display control channel message
01037 if { [info exists buffer] } {
01038 if { $VERBOSE } {
01039 foreach line [split $buffer \n] {
01040 DisplayMsg $s "C: $line" control
01041 }
01042 }
01043 }
01044
01045 # Rather than throwing an error in the event loop, set the ftp(Error)
01046 # variable to hold the message so that it can later be thrown after the
01047 # the StateHandler has completed.
01048
01049 if { [info exists errmsg] } {
01050 set ftp(Error) $errmsg
01051 }
01052
01053 # If operating asynchronously, commence next state
01054 if {$nextState && [info exists ftp(NextState)] && [llength $ftp(NextState)]} {
01055 # Pop the head of the NextState queue
01056 set ftp(State) [lindex $ftp(NextState) 0]
01057 set ftp(NextState) [lreplace $ftp(NextState) 0 0]
01058 StateHandler $s
01059 }
01060
01061 # enable fileevent on control socket again
01062 #fileevent $ftp(CtrlSock) readable [list ::ftp::StateHandler $ftp(CtrlSock)]
01063
01064 }
01065
01066
01067
01068
01069
01070
01071
01072
01073
01074
01075
01076
01077
01078
01079 ret ::ftp::Type (type s , optional type ="") {
01080 upvar ::ftp::ftp$s ftp
01081
01082 if { ![info exists ftp(State)] } {
01083 if { ![string is digit -strict $s] } {
01084 DisplayMsg $s "Bad connection name \"$s\"" error
01085 } else {
01086 DisplayMsg $s "Not connected!" error
01087 }
01088 return {}
01089 }
01090
01091 # return current type
01092 if { $type == "" } {
01093 return $ftp(Type)
01094 }
01095
01096 # save current type
01097 set old_type $ftp(Type)
01098
01099 set ftp(Type) $type
01100 set ftp(State) type
01101 StateHandler $s
01102
01103 # wait for synchronization
01104 set rc [WaitOrTimeout $s]
01105 if { $rc } {
01106 return $ftp(Type)
01107 } else {
01108 # restore old type
01109 set ftp(Type) $old_type
01110 return {}
01111 }
01112 }
01113
01114
01115
01116
01117
01118
01119
01120
01121
01122
01123
01124
01125
01126
01127
01128
01129
01130
01131
01132
01133 ret ::ftp::NList (type s , optional dir ="") {
01134 upvar ::ftp::ftp$s ftp
01135
01136 if { ![info exists ftp(State)] } {
01137 if { ![string is digit -strict $s] } {
01138 DisplayMsg $s "Bad connection name \"$s\"" error
01139 } else {
01140 DisplayMsg $s "Not connected!" error
01141 }
01142 return {}
01143 }
01144
01145 set ftp(List) {}
01146 if { $dir == "" } {
01147 set ftp(Dir) ""
01148 } else {
01149 set ftp(Dir) " $dir"
01150 }
01151
01152 # save current type and force ascii mode
01153 set old_type $ftp(Type)
01154 if { $ftp(Type) != "ascii" } {
01155 if {[string length $ftp(Command)]} {
01156 set ftp(NextState) [list nlist_$ftp(Mode) type_change list_last]
01157 set ftp(type:changeto) $old_type
01158 Type $s ascii
01159 return {}
01160 }
01161 Type $s ascii
01162 }
01163
01164 set ftp(State) nlist_$ftp(Mode)
01165 StateHandler $s
01166
01167 # wait for synchronization
01168 set rc [WaitOrTimeout $s]
01169
01170 # restore old type
01171 if { [Type $s] != $old_type } {
01172 Type $s $old_type
01173 }
01174
01175 unset ftp(Dir)
01176 if { $rc } {
01177 return [lsort [split [string trim $ftp(List) \n] \n]]
01178 } else {
01179 CloseDataConn $s
01180 return {}
01181 }
01182 }
01183
01184
01185
01186
01187
01188
01189
01190
01191
01192
01193
01194
01195
01196
01197
01198
01199
01200
01201
01202
01203 ret ::ftp::List (type s , optional dir ="") {
01204
01205 upvar ::ftp::ftp$s ftp
01206
01207 if { ![info exists ftp(State)] } {
01208 if { ![string is digit -strict $s] } {
01209 DisplayMsg $s "Bad connection name \"$s\"" error
01210 } else {
01211 DisplayMsg $s "Not connected!" error
01212 }
01213 return {}
01214 }
01215
01216 set ftp(List) {}
01217 if { $dir == "" } {
01218 set ftp(Dir) ""
01219 } else {
01220 set ftp(Dir) " $dir"
01221 }
01222
01223 # save current type and force ascii mode
01224
01225 set old_type $ftp(Type)
01226 if { ![string equal "$ftp(Type)" "ascii"] } {
01227 if {[string length $ftp(Command)]} {
01228 set ftp(NextState) [list list_$ftp(Mode) type_change list_last]
01229 set ftp(type:changeto) $old_type
01230 Type $s ascii
01231 return {}
01232 }
01233 Type $s ascii
01234 }
01235
01236 set ftp(State) list_$ftp(Mode)
01237 StateHandler $s
01238
01239 # wait for synchronization
01240
01241 set rc [WaitOrTimeout $s]
01242
01243 # restore old type
01244
01245 if { ![string equal "[Type $s]" "$old_type"] } {
01246 Type $s $old_type
01247 }
01248
01249 unset ftp(Dir)
01250 if { $rc } {
01251 return [ListPostProcess $ftp(List)]
01252 } else {
01253 CloseDataConn $s
01254 return {}
01255 }
01256 }
01257
01258 ret ::ftp::ListPostProcess l (
01259
01260 # type clear ", type total"-, type line
01261
01262 , type set , type l [, type split $, type l "\, type n"]
01263 , type set , type index [, type lsearch -, type regexp $, type l "^, type total"]
01264 , type if , optional $index =!= "-1" , optional
01265 set =l [lreplace =$l $index =$index]
01266
01267
01268 # , type clear , type blank , type line
01269
01270 , type set , type index [, type lsearch -, type regexp $, type l "^$"]
01271 , type if , optional $index =!= "-1" , optional
01272 set =l [lreplace =$l $index =$index]
01273
01274
01275 , type return $, type l
01276 )
01277
01278 #############################################################################
01279 #
01280 # FileSize --
01281 #
01282 # REMOTE FILE SIZE - This command gets the file size of the
01283 # file on the remote machine.
01284 # ATTENTION! Doesn't work properly in ascii mode!
01285 # (exported)
01286 #
01287 # Arguments:
01288 # filename - specifies the remote file name
01289 #
01290 # Returns:
01291 # size - files size in bytes or {} in error cases
01292
01293 ret ::ftp::FileSize (type s , optional filename ="") {
01294 upvar ::ftp::ftp$s ftp
01295
01296 if { ![info exists ftp(State)] } {
01297 if { ![string is digit -strict $s] } {
01298 DisplayMsg $s "Bad connection name \"$s\"" error
01299 } else {
01300 DisplayMsg $s "Not connected!" error
01301 }
01302 return {}
01303 }
01304
01305 if { $filename == "" } {
01306 return {}
01307 }
01308
01309 set ftp(File) $filename
01310 set ftp(FileSize) 0
01311
01312 set ftp(State) size
01313 StateHandler $s
01314
01315 # wait for synchronization
01316 set rc [WaitOrTimeout $s]
01317
01318 if {![string length $ftp(Command)]} {
01319 unset ftp(File)
01320 }
01321
01322 if { $rc } {
01323 return $ftp(FileSize)
01324 } else {
01325 return {}
01326 }
01327 }
01328
01329
01330
01331
01332
01333
01334
01335
01336
01337
01338
01339
01340
01341
01342
01343
01344
01345
01346
01347
01348 ret ::ftp::ModTime (type s , optional filename ="" , optional datetime ="") {
01349 upvar ::ftp::ftp$s ftp
01350
01351 if { ![info exists ftp(State)] } {
01352 if { ![string is digit -strict $s] } {
01353 DisplayMsg $s "Bad connection name \"$s\"" error
01354 } else {
01355 DisplayMsg $s "Not connected!" error
01356 }
01357 return {}
01358 }
01359
01360 if { $filename == "" } {
01361 return {}
01362 }
01363
01364 set ftp(File) $filename
01365
01366 if {$datetime != ""} {
01367 set datetime [clock format $datetime -format "%Y%m%d%H%M%S"]
01368 }
01369 set ftp(DateTime) $datetime
01370
01371 set ftp(State) modtime
01372 StateHandler $s
01373
01374 # wait for synchronization
01375 set rc [WaitOrTimeout $s]
01376
01377 if {![string length $ftp(Command)]} {
01378 unset ftp(File)
01379 }
01380 if { ![string length $ftp(Command)] && $rc } {
01381 return [ModTimePostProcess $ftp(DateTime)]
01382 } else {
01383 return {}
01384 }
01385 }
01386
01387 ret ::ftp::ModTimePostProcess (type clock) {
01388 foreach {year month day hour min sec} {1 1 1 1 1 1} break
01389
01390 # Bug #478478. Special code to detect ftp servers with a Y2K patch
01391 # gone bad and delivering, hmmm, non-standard date information.
01392
01393 if {[string length $clock] == 15} {
01394 scan $clock "%2s%3s%2s%2s%2s%2s%2s" cent year month day hour min sec
01395 set year [expr {($cent * 100) + $year}]
01396 log::log warning "data | W: server with non-standard time, bad Y2K patch."
01397 } else {
01398 scan $clock "%4s%2s%2s%2s%2s%2s" year month day hour min sec
01399 }
01400
01401 set clock [clock scan "$month/$day/$year $hour:$min:$sec" -gmt 1]
01402 return $clock
01403 }
01404
01405
01406
01407
01408
01409
01410
01411
01412
01413
01414
01415
01416
01417
01418 ret ::ftp::Pwd (type s ) {
01419 upvar ::ftp::ftp$s ftp
01420
01421 if { ![info exists ftp(State)] } {
01422 if { ![string is digit -strict $s] } {
01423 DisplayMsg $s "Bad connection name \"$s\"" error
01424 } else {
01425 DisplayMsg $s "Not connected!" error
01426 }
01427 return {}
01428 }
01429
01430 set ftp(Dir) {}
01431
01432 set ftp(State) pwd
01433 StateHandler $s
01434
01435 # wait for synchronization
01436 set rc [WaitOrTimeout $s]
01437
01438 if { $rc } {
01439 return $ftp(Dir)
01440 } else {
01441 return {}
01442 }
01443 }
01444
01445
01446
01447
01448
01449
01450
01451
01452
01453
01454
01455
01456
01457
01458
01459 ret ::ftp::Cd (type s , optional dir ="") {
01460 upvar ::ftp::ftp$s ftp
01461
01462 if { ![info exists ftp(State)] } {
01463 if { ![string is digit -strict $s] } {
01464 DisplayMsg $s "Bad connection name \"$s\"" error
01465 } else {
01466 DisplayMsg $s "Not connected!" error
01467 }
01468 return 0
01469 }
01470
01471 if { $dir == "" } {
01472 set ftp(Dir) ""
01473 } else {
01474 set ftp(Dir) " $dir"
01475 }
01476
01477 set ftp(State) cd
01478 StateHandler $s
01479
01480 # wait for synchronization
01481 set rc [WaitOrTimeout $s]
01482
01483 if {![string length $ftp(Command)]} {
01484 unset ftp(Dir)
01485 }
01486
01487 if { $rc } {
01488 return 1
01489 } else {
01490 return 0
01491 }
01492 }
01493
01494
01495
01496
01497
01498
01499
01500
01501
01502
01503
01504
01505
01506
01507
01508
01509
01510 ret ::ftp::MkDir (type s , type dir) {
01511 upvar ::ftp::ftp$s ftp
01512
01513 if { ![info exists ftp(State)] } {
01514 DisplayMsg $s "Not connected!" error
01515 return 0
01516 }
01517
01518 set ftp(Dir) $dir
01519
01520 set ftp(State) mkdir
01521 StateHandler $s
01522
01523 # wait for synchronization
01524 set rc [WaitOrTimeout $s]
01525
01526 if {![string length $ftp(Command)]} {
01527 unset ftp(Dir)
01528 }
01529
01530 if { $rc } {
01531 return 1
01532 } else {
01533 return 0
01534 }
01535 }
01536
01537
01538
01539
01540
01541
01542
01543
01544
01545
01546
01547
01548
01549
01550
01551
01552
01553 ret ::ftp::RmDir (type s , type dir) {
01554 upvar ::ftp::ftp$s ftp
01555
01556 if { ![info exists ftp(State)] } {
01557 DisplayMsg $s "Not connected!" error
01558 return 0
01559 }
01560
01561 set ftp(Dir) $dir
01562
01563 set ftp(State) rmdir
01564 StateHandler $s
01565
01566 # wait for synchronization
01567 set rc [WaitOrTimeout $s]
01568
01569 if {![string length $ftp(Command)]} {
01570 unset ftp(Dir)
01571 }
01572
01573 if { $rc } {
01574 return 1
01575 } else {
01576 return 0
01577 }
01578 }
01579
01580
01581
01582
01583
01584
01585
01586
01587
01588
01589
01590
01591
01592
01593
01594
01595 ret ::ftp::Delete (type s , type file) {
01596 upvar ::ftp::ftp$s ftp
01597
01598 if { ![info exists ftp(State)] } {
01599 DisplayMsg $s "Not connected!" error
01600 return 0
01601 }
01602
01603 set ftp(File) $file
01604
01605 set ftp(State) delete
01606 StateHandler $s
01607
01608 # wait for synchronization
01609 set rc [WaitOrTimeout $s]
01610
01611 if {![string length $ftp(Command)]} {
01612 unset ftp(File)
01613 }
01614
01615 if { $rc } {
01616 return 1
01617 } else {
01618 return 0
01619 }
01620 }
01621
01622
01623
01624
01625
01626
01627
01628
01629
01630
01631
01632
01633
01634
01635
01636
01637
01638
01639 ret ::ftp::Rename (type s , type from , type to) {
01640 upvar ::ftp::ftp$s ftp
01641
01642 if { ![info exists ftp(State)] } {
01643 DisplayMsg $s "Not connected!" error
01644 return 0
01645 }
01646
01647 set ftp(RenameFrom) $from
01648 set ftp(RenameTo) $to
01649
01650 set ftp(State) rename
01651
01652 StateHandler $s
01653
01654 # wait for synchronization
01655 set rc [WaitOrTimeout $s]
01656
01657 if {![string length $ftp(Command)]} {
01658 unset ftp(RenameFrom)
01659 unset ftp(RenameTo)
01660 }
01661
01662 if { $rc } {
01663 return 1
01664 } else {
01665 return 0
01666 }
01667 }
01668
01669
01670
01671
01672
01673
01674
01675
01676
01677
01678 ret ::ftp::ElapsedTime (type s , type stop_, type time) {
01679 variable VERBOSE
01680 upvar ::ftp::ftp$s ftp
01681
01682 set elapsed [expr {$stop_time - $ftp(Start_Time)}]
01683 if { $elapsed == 0 } {
01684 set elapsed 1
01685 }
01686 set persec [expr {$ftp(Total) / $elapsed}]
01687 if { $VERBOSE } {
01688 DisplayMsg $s "$ftp(Total) bytes sent in $elapsed seconds ($persec Bytes/s)"
01689 }
01690 return
01691 }
01692
01693
01694
01695
01696
01697
01698
01699
01700
01701
01702
01703
01704
01705
01706
01707
01708
01709
01710
01711
01712 ret ::ftp::Put (type s , type args) {
01713 upvar ::ftp::ftp$s ftp
01714
01715 if { ![info exists ftp(State)] } {
01716 DisplayMsg $s "Not connected!" error
01717 return 0
01718 }
01719 if {([llength $args] < 1) || ([llength $args] > 4)} {
01720 DisplayMsg $s \
01721 "wrong # args: should be \"ftp::Put handle (-data \"data\" | -channel chan | localFilename) remoteFilename\"" error
01722 return 0
01723 }
01724
01725 set ftp(inline) 0
01726 set flags 1
01727 set source ""
01728 set dest ""
01729 foreach arg $args {
01730 if {[string equal $arg "--"]} {
01731 set flags 0
01732 } elseif {($flags) && ([string equal $arg "-data"])} {
01733 set ftp(inline) 1
01734 set ftp(filebuffer) ""
01735 } elseif {($flags) && ([string equal $arg "-channel"])} {
01736 set ftp(inline) 2
01737 } elseif {$source == ""} {
01738 set source $arg
01739 } elseif {$dest == ""} {
01740 set dest $arg
01741 } else {
01742 DisplayMsg $s "wrong # args: should be \"ftp::Put handle (-data \"data\" | -channel chan | localFilename) remoteFilename\"" error
01743 return 0
01744 }
01745 }
01746
01747 if {($source == "")} {
01748 DisplayMsg $s "Must specify a valid data source to Put" error
01749 return 0
01750 }
01751
01752 set ftp(RemoteFilename) $dest
01753
01754 if {$ftp(inline) == 1} {
01755 set ftp(PutData) $source
01756 if { $dest == "" } {
01757 set dest ftp.tmp
01758 }
01759 set ftp(RemoteFilename) $dest
01760 } else {
01761 if {$ftp(inline) == 0} {
01762 # File transfer
01763
01764 set ftp(PutData) ""
01765 if { ![file exists $source] } {
01766 DisplayMsg $s "File \"$source\" not exist" error
01767 return 0
01768 }
01769 if { $dest == "" } {
01770 set dest [file tail $source]
01771 }
01772 set ftp(LocalFilename) $source
01773 set ftp(SourceCI) [open $ftp(LocalFilename) r]
01774 } else {
01775 # Channel transfer. We fake the rest of the system into
01776 # believing that a file transfer is happening. This makes
01777 # the handling easier.
01778
01779 set ftp(SourceCI) $source
01780 set ftp(inline) 0
01781 }
01782 set ftp(RemoteFilename) $dest
01783
01784 # TODO: read from source file asynchronously
01785 if { [string equal $ftp(Type) "ascii"] } {
01786 fconfigure $ftp(SourceCI) -buffering line -blocking 1
01787 } else {
01788 fconfigure $ftp(SourceCI) -buffering line -translation binary -blocking 1
01789 }
01790 }
01791
01792 set ftp(State) put_$ftp(Mode)
01793 StateHandler $s
01794
01795 # wait for synchronization
01796 set rc [WaitOrTimeout $s]
01797 if { $rc } {
01798 if {![string length $ftp(Command)]} {
01799 ElapsedTime $s [clock seconds]
01800 }
01801 return 1
01802 } else {
01803 CloseDataConn $s
01804 return 0
01805 }
01806 }
01807
01808
01809
01810
01811
01812
01813
01814
01815
01816
01817
01818
01819
01820
01821
01822
01823
01824
01825
01826
01827 ret ::ftp::Append (type s , type args) {
01828 upvar ::ftp::ftp$s ftp
01829
01830 if { ![info exists ftp(State)] } {
01831 DisplayMsg $s "Not connected!" error
01832 return 0
01833 }
01834
01835 if {([llength $args] < 1) || ([llength $args] > 4)} {
01836 DisplayMsg $s "wrong # args: should be \"ftp::Append handle (-data \"data\" | -channel chan | localFilename) remoteFilename\"" error
01837 return 0
01838 }
01839
01840 set ftp(inline) 0
01841 set flags 1
01842 set source ""
01843 set dest ""
01844 foreach arg $args {
01845 if {[string equal $arg "--"]} {
01846 set flags 0
01847 } elseif {($flags) && ([string equal $arg "-data"])} {
01848 set ftp(inline) 1
01849 set ftp(filebuffer) ""
01850 } elseif {($flags) && ([string equal $arg "-channel"])} {
01851 set ftp(inline) 2
01852 } elseif {$source == ""} {
01853 set source $arg
01854 } elseif {$dest == ""} {
01855 set dest $arg
01856 } else {
01857 DisplayMsg $s "wrong # args: should be \"ftp::Append handle (-data \"data\" | -channel chan | localFilename) remoteFilename\"" error
01858 return 0
01859 }
01860 }
01861
01862 if {($source == "")} {
01863 DisplayMsg $s "Must specify a valid data source to Append" error
01864 return 0
01865 }
01866
01867 set ftp(RemoteFilename) $dest
01868
01869 if {$ftp(inline) == 1} {
01870 set ftp(PutData) $source
01871 if { $dest == "" } {
01872 set dest ftp.tmp
01873 }
01874 set ftp(RemoteFilename) $dest
01875 } else {
01876 if {$ftp(inline) == 0} {
01877 # File transfer
01878
01879 set ftp(PutData) ""
01880 if { ![file exists $source] } {
01881 DisplayMsg $s "File \"$source\" not exist" error
01882 return 0
01883 }
01884
01885 if { $dest == "" } {
01886 set dest [file tail $source]
01887 }
01888
01889 set ftp(LocalFilename) $source
01890 set ftp(SourceCI) [open $ftp(LocalFilename) r]
01891 } else {
01892 # Channel transfer. We fake the rest of the system into
01893 # believing that a file transfer is happening. This makes
01894 # the handling easier.
01895
01896 set ftp(SourceCI) $source
01897 set ftp(inline) 0
01898 }
01899 set ftp(RemoteFilename) $dest
01900
01901 if { [string equal $ftp(Type) "ascii"] } {
01902 fconfigure $ftp(SourceCI) -buffering line -blocking 1
01903 } else {
01904 fconfigure $ftp(SourceCI) -buffering line -translation binary \
01905 -blocking 1
01906 }
01907 }
01908
01909 set ftp(State) append_$ftp(Mode)
01910 StateHandler $s
01911
01912 # wait for synchronization
01913 set rc [WaitOrTimeout $s]
01914 if { $rc } {
01915 if {![string length $ftp(Command)]} {
01916 ElapsedTime $s [clock seconds]
01917 }
01918 return 1
01919 } else {
01920 CloseDataConn $s
01921 return 0
01922 }
01923 }
01924
01925
01926
01927
01928
01929
01930
01931
01932
01933
01934
01935
01936
01937
01938
01939
01940
01941
01942 ret ::ftp::Get (type s , type args) {
01943 upvar ::ftp::ftp$s ftp
01944
01945 if { ![info exists ftp(State)] } {
01946 DisplayMsg $s "Not connected!" error
01947 return 0
01948 }
01949
01950 if {([llength $args] < 1) || ([llength $args] > 4)} {
01951 DisplayMsg $s "wrong # args: should be \"ftp::Get handle remoteFile ?(-variable varName | -channel chan | localFilename)?\"" error
01952 return 0
01953 }
01954
01955 set ftp(inline) 0
01956 set flags 1
01957 set source ""
01958 set dest ""
01959 set varname "**NONE**"
01960 foreach arg $args {
01961 if {[string equal $arg "--"]} {
01962 set flags 0
01963 } elseif {($flags) && ([string equal $arg "-variable"])} {
01964 set ftp(inline) 1
01965 set ftp(filebuffer) ""
01966 } elseif {($flags) && ([string equal $arg "-channel"])} {
01967 set ftp(inline) 2
01968 } elseif {($ftp(inline) == 1) && ([string equal $varname "**NONE**"])} {
01969 set varname $arg
01970 set ftp(get:varname) $varname
01971 } elseif {($ftp(inline) == 2) && ([string equal $varname "**NONE**"])} {
01972 set ftp(get:channel) $arg
01973 } elseif {$source == ""} {
01974 set source $arg
01975 } elseif {$dest == ""} {
01976 set dest $arg
01977 } else {
01978 DisplayMsg $s "wrong # args: should be \"ftp::Get handle remoteFile
01979 ?(-variable varName | -channel chan | localFilename)?\"" error
01980 return 0
01981 }
01982 }
01983
01984 if {($ftp(inline) != 0) && ($dest != "")} {
01985 DisplayMsg $s "Cannot return data in a variable or channel, and place it in destination file." error
01986 return 0
01987 }
01988
01989 if {$source == ""} {
01990 DisplayMsg $s "Must specify a valid data source to Get" error
01991 return 0
01992 }
01993
01994 if {$ftp(inline) == 0} {
01995 if { $dest == "" } {
01996 set dest $source
01997 } else {
01998 if {[file isdirectory $dest]} {
01999 set dest [file join $dest [file tail $source]]
02000 }
02001 }
02002 if {![file exists [file dirname $dest]]} {
02003 return -code error "ftp::Get, directory \"[file dirname $dest]\" for destination \"$dest\" does not exist"
02004 }
02005 set ftp(LocalFilename) $dest
02006 }
02007
02008 set ftp(RemoteFilename) $source
02009
02010 if {$ftp(inline) == 2} {
02011 set ftp(inline) 0
02012 }
02013 set ftp(State) get_$ftp(Mode)
02014 StateHandler $s
02015
02016 # wait for synchronization
02017 set rc [WaitOrTimeout $s]
02018
02019 # It is important to unset 'get:channel' in all cases or it will
02020 # interfere with any following ftp command (as its existence
02021 # suppresses the closing of the destination channel identifier
02022 # (DestCI). We cannot do it earlier than just before the 'return'
02023 # or code depending on it for the current command may not execute
02024 # correctly.
02025
02026 if { $rc } {
02027 if {![string length $ftp(Command)]} {
02028 ElapsedTime $s [clock seconds]
02029 if {$ftp(inline)} {
02030 catch {unset ftp(get:channel)}
02031 upvar $varname returnData
02032 set returnData $ftp(GetData)
02033 }
02034 }
02035 # catch {unset ftp(get:channel)}
02036 # SF Bug 1708350. DISABLED. In async mode (Open -command) the
02037 # unset here causes HandleData to blow up, see marker <@>. In
02038 # essence in async mode HandleData can be entered multiple
02039 # times, and unsetting get:channel here causes it to think
02040 # that the data goes into a local file, not a channel, but the
02041 # state does not contain local file information, so an error
02042 # is thrown. Removing the catch here seems to fix it without
02043 # adverse effects elsewhere. Maybe. We hope.
02044 return 1
02045 } else {
02046 if {$ftp(inline)} {
02047 catch {unset ftp(get:channel)}
02048 return ""
02049 }
02050 CloseDataConn $s
02051 catch {unset ftp(get:channel)}
02052 return 0
02053 }
02054 }
02055
02056
02057
02058
02059
02060
02061
02062
02063
02064
02065
02066
02067
02068
02069
02070
02071
02072
02073 ret ::ftp::Reget (type s , type source , optional dest ="" , optional from_bytes =0 , optional till_bytes =-1) {
02074 upvar ::ftp::ftp$s ftp
02075
02076 if { ![info exists ftp(State)] } {
02077 DisplayMsg $s "Not connected!" error
02078 return 0
02079 }
02080
02081 if { $dest == "" } {
02082 set dest $source
02083 }
02084 if {![file exists [file dirname $dest]]} {
02085 return -code error \
02086 "ftp::Reget, directory \"[file dirname $dest]\" for destination \"$dest\" does not exist"
02087 }
02088
02089 set ftp(RemoteFilename) $source
02090 set ftp(LocalFilename) $dest
02091 set ftp(From) $from_bytes
02092
02093
02094 # Assumes that the local file has a starting offset of $from_bytes
02095 # The following calculation ensures that the download starts from the
02096 # correct offset
02097
02098 if { [file exists $ftp(LocalFilename)] } {
02099 set ftp(FileSize) [ expr {[file size $ftp(LocalFilename)] + $from_bytes }]
02100
02101 if { $till_bytes != -1 } {
02102 set ftp(To) $till_bytes
02103 set ftp(Bytes_to_go) [ expr {$till_bytes - $ftp(FileSize)} ]
02104
02105 if { $ftp(Bytes_to_go) <= 0 } {return 0}
02106
02107 } else {
02108 # till_bytes not set
02109 set ftp(To) end
02110 }
02111
02112 } else {
02113 # local file does not exist
02114 set ftp(FileSize) $from_bytes
02115
02116 if { $till_bytes != -1 } {
02117 set ftp(Bytes_to_go) [ expr {$till_bytes - $from_bytes }]
02118 set ftp(To) $till_bytes
02119 } else {
02120 #till_bytes not set
02121 set ftp(To) end
02122 }
02123 }
02124
02125 set ftp(State) reget_$ftp(Mode)
02126 StateHandler $s
02127
02128 # wait for synchronization
02129 set rc [WaitOrTimeout $s]
02130 if { $rc } {
02131 if {![string length $ftp(Command)]} {
02132 ElapsedTime $s [clock seconds]
02133 }
02134 return 1
02135 } else {
02136 CloseDataConn $s
02137 return 0
02138 }
02139 }
02140
02141
02142
02143
02144
02145
02146
02147
02148
02149
02150
02151
02152
02153
02154
02155
02156
02157
02158
02159
02160 ret ::ftp::Newer (type s , type source , optional dest ="") {
02161 upvar ::ftp::ftp$s ftp
02162
02163 if { ![info exists ftp(State)] } {
02164 DisplayMsg $s "Not connected!" error
02165 return 0
02166 }
02167
02168 if {[string length $ftp(Command)]} {
02169 return -code error "unable to retrieve file asynchronously (not implemented yet)"
02170 }
02171
02172 if { $dest == "" } {
02173 set dest $source
02174 }
02175 if {![file exists [file dirname $dest]]} {
02176 return -code error "ftp::Newer, directory \"[file dirname $dest]\" for destination \"$dest\" does not exist"
02177 }
02178
02179 set ftp(RemoteFilename) $source
02180 set ftp(LocalFilename) $dest
02181
02182 # get remote modification time
02183 set rmt [ModTime $s $ftp(RemoteFilename)]
02184 if { $rmt == "-1" } {
02185 return 0
02186 }
02187
02188 # get local modification time
02189 if { [file exists $ftp(LocalFilename)] } {
02190 set lmt [file mtime $ftp(LocalFilename)]
02191 } else {
02192 set lmt 0
02193 }
02194
02195 # remote file is older than local file
02196 if { $rmt < $lmt } {
02197 return 0
02198 }
02199
02200 # remote file is newer than local file or local file doesn't exist
02201 # get it
02202 set rc [Get $s $ftp(RemoteFilename) $ftp(LocalFilename)]
02203 return $rc
02204
02205 }
02206
02207
02208
02209
02210
02211
02212
02213
02214
02215
02216
02217
02218
02219
02220 ret ::ftp::Quote (type s , type args) {
02221 upvar ::ftp::ftp$s ftp
02222
02223 if { ![info exists ftp(State)] } {
02224 DisplayMsg $s "Not connected!" error
02225 return 0
02226 }
02227
02228 set ftp(Cmd) $args
02229 set ftp(Quote) {}
02230
02231 set ftp(State) quote
02232 StateHandler $s
02233
02234 # wait for synchronization
02235 set rc [WaitOrTimeout $s]
02236
02237 unset ftp(Cmd)
02238
02239 if { $rc } {
02240 return $ftp(Quote)
02241 } else {
02242 return {}
02243 }
02244 }
02245
02246
02247
02248
02249
02250
02251
02252
02253
02254
02255
02256
02257
02258
02259
02260
02261
02262
02263
02264
02265
02266
02267
02268
02269
02270
02271
02272
02273
02274
02275
02276
02277
02278
02279
02280
02281
02282
02283
02284
02285
02286 ret ::ftp::Close (type s ) {
02287 variable connections
02288 upvar ::ftp::ftp$s ftp
02289
02290 if { ![info exists ftp(State)] } {
02291 DisplayMsg $s "Not connected!" error
02292 return 0
02293 }
02294
02295 if {[info exists \
02296 connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost),afterid)]} {
02297 unset connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost),afterid)
02298 unset connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost))
02299 }
02300
02301 set ftp(State) quit
02302 StateHandler $s
02303
02304 # wait for synchronization
02305 WaitOrTimeout $s
02306
02307 catch {close $ftp(CtrlSock)}
02308 catch {unset ftp}
02309 return 1
02310 }
02311
02312 ret ::ftp::LazyClose (type s ) {
02313 variable connections
02314 upvar ::ftp::ftp$s ftp
02315
02316 if { ![info exists ftp(State)] } {
02317 DisplayMsg $s "Not connected!" error
02318 return 0
02319 }
02320
02321 if {[info exists connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost))]} {
02322 set connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost),afterid) \
02323 [after 5000 [list ftp::Close $s]]
02324 }
02325 return 1
02326 }
02327
02328
02329
02330
02331
02332
02333
02334
02335
02336
02337
02338
02339
02340
02341
02342
02343
02344
02345
02346
02347
02348
02349
02350
02351
02352
02353
02354
02355
02356
02357
02358
02359
02360 ret ::ftp::Open (type server , type user , type passwd , type args) {
02361 variable DEBUG
02362 variable VERBOSE
02363 variable serial
02364 variable connections
02365
02366 set s $serial
02367 incr serial
02368 upvar ::ftp::ftp$s ftp
02369 # if { [info exists ftp(State)] } {
02370 # DisplayMsg $s "Mmh, another attempt to open a new connection? There is already a hot wire!" error
02371 # return 0
02372 # }
02373
02374 # default NO DEBUG
02375 if { ![info exists DEBUG] } {
02376 set DEBUG 0
02377 }
02378
02379 # default NO VERBOSE
02380 if { ![info exists VERBOSE] } {
02381 set VERBOSE 0
02382 }
02383
02384 if { $DEBUG } {
02385 DisplayMsg $s "Starting new connection with: "
02386 }
02387
02388 set ftp(inline) 0
02389 set ftp(User) $user
02390 set ftp(Passwd) $passwd
02391 set ftp(RemoteHost) $server
02392 set ftp(LocalHost) [info hostname]
02393 set ftp(DataPort) 0
02394 set ftp(Type) {}
02395 set ftp(Error) ""
02396 set ftp(Progress) {}
02397 set ftp(Command) {}
02398 set ftp(Output) {}
02399 set ftp(Blocksize) 4096
02400 set ftp(Timeout) 600
02401 set ftp(Mode) active
02402 set ftp(Port) 21
02403
02404 set ftp(State) user
02405
02406 # set state var
02407 set ftp(state.control) ""
02408
02409 # Get and set possible options
02410 set options {-blocksize -timeout -mode -port -progress -output -command}
02411 foreach {option value} $args {
02412 if { [lsearch -exact $options $option] != "-1" } {
02413 if { $DEBUG } {
02414 DisplayMsg $s " $option = $value"
02415 }
02416 regexp -- {^-(.?)(.*)$} $option all first rest
02417 set option "[string toupper $first]$rest"
02418 set ftp($option) $value
02419 }
02420 }
02421 if { $DEBUG && ([llength $args] == 0) } {
02422 DisplayMsg $s " no option"
02423 }
02424
02425 if {[info exists \
02426 connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost),afterid)]} {
02427 after cancel $connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost),afterid)
02428 Command $ftp(Command) connect $connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost))
02429 return $connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost))
02430 }
02431
02432
02433 # No call of StateHandler is required at this time.
02434 # StateHandler at first time is called automatically
02435 # by a fileevent for the control channel.
02436
02437 # Try to open a control connection
02438 if { ![OpenControlConn $s [expr {[string length $ftp(Command)] > 0}]] } {
02439 return -1
02440 }
02441
02442 # waits for synchronization
02443 # 0 ... Not logged in
02444 # 1 ... User logged in
02445 if {[string length $ftp(Command)]} {
02446 # Don't wait - asynchronous operation
02447 set ftp(NextState) {type connect_last}
02448 set connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost)) $s
02449 return $s
02450 } elseif { [WaitOrTimeout $s] } {
02451 # default type is binary
02452 Type $s binary
02453 set connections($ftp(User),$ftp(Passwd),$ftp(RemoteHost)) $s
02454 Command $ftp(Command) connect $s
02455 return $s
02456 } else {
02457 # close connection if not logged in
02458 Close $s
02459 return -1
02460 }
02461 }
02462
02463
02464
02465
02466
02467
02468
02469
02470
02471
02472 ret ::ftp::CopyNext (type s , type bytes , optional error ={)} {
02473 upvar ::ftp::ftp$s ftp
02474 variable DEBUG
02475 variable VERBOSE
02476
02477 # summary bytes
02478
02479 incr ftp(Total) $bytes
02480
02481 # update bytes_to_go and blocksize
02482
02483 if { [info exists ftp(Bytes_to_go)] } {
02484 ftp = (Bytes_to_go) [expr {$ftp(Bytes_to_go) - $bytes}]
02485
02486 if { $ftp(Blocksize) <= $ftp(Bytes_to_go) } {
02487 blocksize = $ftp(Blocksize)
02488 } else {
02489 blocksize = $ftp(Bytes_to_go)
02490 }
02491 } else {
02492 blocksize = $ftp(Blocksize)
02493 }
02494
02495
02496
02497 if { ([info exists ftp(Progress)]) && \
02498 [string length $ftp(Progress)] && \
02499 ([info commands [lindex $ftp(Progress) 0]] != "") } {
02500 eval $ftp(Progress) $ftp(Total)
02501 }
02502
02503
02504
02505 catch {after cancel $ftp(Wait)}
02506 ftp = (Wait) [after [expr {$ftp(Timeout) * 1000}] [namespace current]::Timeout $s]
02507
02508 if { $DEBUG } {
02509 DisplayMsg $s "-> $ftp(Total) bytes $ftp(SourceCI) -> $ftp(DestCI)"
02510 }
02511
02512 if { $error != "" } {
02513
02514
02515
02516 if {![info exists ftp(get:channel)]} {
02517 catch {close $ftp(DestCI)}
02518 }
02519 catch {close $ftp(SourceCI)}
02520 catch {un ftp = (state.data)}
02521 DisplayMsg $s $error error
02522
02523 } elseif { ([eof $ftp(SourceCI)] || ($blocksize <= 0)) } {
02524
02525
02526
02527 if {![info exists ftp(get:channel)]} {
02528 close $ftp(DestCI)
02529 }
02530 close $ftp(SourceCI)
02531 catch {un ftp = (state.data)}
02532 if { $VERBOSE } {
02533 DisplayMsg $s "D: Port closed" data
02534 }
02535
02536 } else {
02537 fcopy $ftp(SourceCI) $ftp(DestCI) \
02538 -command [list [namespace current]::CopyNext $s] \
02539 -size $blocksize
02540 }
02541 return
02542 }
02543
02544
02545
02546
02547
02548
02549
02550
02551
02552
02553 ret ::ftp::HandleData (type s , type sock) {
02554 upvar ::ftp::ftp$s ftp
02555
02556 # Turn off any fileevent handlers
02557
02558 fileevent $sock writable {}
02559 fileevent $sock readable {}
02560
02561 # create local file for ftp::Get
02562
02563 if { [string match "get*" $ftp(State)] && (!$ftp(inline))} {
02564
02565 # A channel was specified by the caller. Use that instead of a
02566 # file.
02567
02568 # SF Bug 1708350 <@>
02569 if {[info exists ftp(get:channel)]} {
02570 set ftp(DestCI) $ftp(get:channel)
02571 set rc 0
02572 } else {
02573 set rc [catch {set ftp(DestCI) [open $ftp(LocalFilename) w]} msg]
02574 }
02575 if { $rc != 0 } {
02576 DisplayMsg $s "$msg" error
02577 return 0
02578 }
02579 # TODO: Use non-blocking I/O
02580 if { [string equal $ftp(Type) "ascii"] } {
02581 fconfigure $ftp(DestCI) -buffering line -blocking 1
02582 } else {
02583 fconfigure $ftp(DestCI) -buffering line -translation binary -blocking 1
02584 }
02585 }
02586
02587 # append local file for ftp::Reget
02588
02589 if { [string match "reget*" $ftp(State)] } {
02590 set rc [catch {set ftp(DestCI) [open $ftp(LocalFilename) a]} msg]
02591 if { $rc != 0 } {
02592 DisplayMsg $s "$msg" error
02593 return 0
02594 }
02595 # TODO: Use non-blocking I/O
02596 if { [string equal $ftp(Type) "ascii"] } {
02597 fconfigure $ftp(DestCI) -buffering line -blocking 1
02598 } else {
02599 fconfigure $ftp(DestCI) -buffering line -translation binary -blocking 1
02600 }
02601 }
02602
02603
02604 set ftp(Total) 0
02605 set ftp(Start_Time) [clock seconds]
02606
02607 # calculate blocksize
02608
02609 if { [ info exists ftp(Bytes_to_go) ] } {
02610
02611 if { $ftp(Blocksize) <= $ftp(Bytes_to_go) } {
02612 set Blocksize $ftp(Blocksize)
02613 } else {
02614 set Blocksize $ftp(Bytes_to_go)
02615 }
02616
02617 } else {
02618 set Blocksize $ftp(Blocksize)
02619 }
02620
02621 # perform fcopy
02622 fcopy $ftp(SourceCI) $ftp(DestCI) \
02623 -command [list [namespace current]::CopyNext $s ] \
02624 -size $Blocksize
02625 return 1
02626 }
02627
02628
02629
02630
02631
02632
02633
02634
02635
02636
02637 ret ::ftp::HandleList (type s , type sock) {
02638 upvar ::ftp::ftp$s ftp
02639 variable VERBOSE
02640
02641 if { ![eof $sock] } {
02642 set buffer [read $sock]
02643 if { $buffer != "" } {
02644 set ftp(List) [append ftp(List) $buffer]
02645 }
02646 } else {
02647 close $sock
02648 catch {unset ftp(state.data)}
02649 if { $VERBOSE } {
02650 DisplayMsg $s "D: Port closed" data
02651 }
02652 }
02653 return
02654 }
02655
02656
02657
02658
02659
02660
02661
02662
02663
02664
02665
02666 ret ::ftp::HandleVar (type s , type sock) {
02667 upvar ::ftp::ftp$s ftp
02668 variable VERBOSE
02669
02670 if {$ftp(Start_Time) == -1} {
02671 set ftp(Start_Time) [clock seconds]
02672 }
02673
02674 if { ![eof $sock] } {
02675 set buffer [read $sock]
02676 if { $buffer != "" } {
02677 append ftp(GetData) $buffer
02678 incr ftp(Total) [string length $buffer]
02679 }
02680 } else {
02681 close $sock
02682 catch {unset ftp(state.data)}
02683 if { $VERBOSE } {
02684 DisplayMsg $s "D: Port closed" data
02685 }
02686 }
02687 return
02688 }
02689
02690
02691
02692
02693
02694
02695
02696
02697
02698
02699
02700 ret ::ftp::HandleOutput (type s , type sock) {
02701 upvar ::ftp::ftp$s ftp
02702 variable VERBOSE
02703
02704 if {$ftp(Start_Time) == -1} {
02705 set ftp(Start_Time) [clock seconds]
02706 }
02707
02708 if { $ftp(Total) < [string length $ftp(PutData)] } {
02709 set substr [string range $ftp(PutData) $ftp(Total) \
02710 [expr {$ftp(Total) + $ftp(Blocksize)}]]
02711 if {[catch {puts -nonewline $sock "$substr"} result]} {
02712 close $sock
02713 catch {unset ftp(state.data)}
02714 if { $VERBOSE } {
02715 DisplayMsg $s "D: Port closed" data
02716 }
02717 } else {
02718 incr ftp(Total) [string length $substr]
02719 }
02720 } else {
02721 fileevent $sock writable {}
02722 close $sock
02723 catch {unset ftp(state.data)}
02724 if { $VERBOSE } {
02725 DisplayMsg $s "D: Port closed" data
02726 }
02727 }
02728 return
02729 }
02730
02731
02732
02733
02734
02735
02736
02737
02738
02739
02740
02741
02742
02743 ret ::ftp::CloseDataConn (type s ) {
02744 upvar ::ftp::ftp$s ftp
02745
02746 # Protect the destination channel from destruction if it came
02747 # from the caller. Closing it is not our responsibility.
02748
02749 if {[info exists ftp(get:channel)]} {
02750 catch {unset ftp(get:channel)}
02751 catch {unset ftp(DestCI)}
02752 }
02753
02754 catch {after cancel $ftp(Wait)}
02755 catch {fileevent $ftp(DataSock) readable {}}
02756 catch {close $ftp(DataSock); unset ftp(DataSock)}
02757 catch {close $ftp(DestCI); unset ftp(DestCI)}
02758 catch {close $ftp(SourceCI); unset ftp(SourceCI)}
02759 catch {close $ftp(DummySock); unset ftp(DummySock)}
02760 return
02761 }
02762
02763
02764
02765
02766
02767
02768
02769
02770
02771
02772
02773
02774
02775
02776
02777 ret ::ftp::InitDataConn (type s , type sock , type addr , type port) {
02778 upvar ::ftp::ftp$s ftp
02779 variable VERBOSE
02780
02781 # If the new channel is accepted, the dummy channel will be closed
02782
02783 catch {close $ftp(DummySock); unset ftp(DummySock)}
02784
02785 set ftp(state.data) 0
02786
02787 # Configure translation and blocking modes
02788
02789 set blocking 1
02790 if {[string length $ftp(Command)]} {
02791 set blocking 0
02792 }
02793
02794 if { [string equal $ftp(Type) "ascii"] } {
02795 fconfigure $sock -buffering line -blocking $blocking
02796 } else {
02797 fconfigure $sock -buffering line -translation binary -blocking $blocking
02798 }
02799
02800 # assign fileevent handlers, source and destination CI (Channel Identifier)
02801
02802 # NB: this really does need to be -regexp [PT] 18Mar03
02803 switch -regexp -- $ftp(State) {
02804 list {
02805 fileevent $sock readable [list [namespace current]::HandleList $s $sock]
02806 set ftp(SourceCI) $sock
02807 }
02808 get {
02809 if {$ftp(inline)} {
02810 set ftp(GetData) ""
02811 set ftp(Start_Time) -1
02812 set ftp(Total) 0
02813 fileevent $sock readable [list [namespace current]::HandleVar $s $sock]
02814 } else {
02815 fileevent $sock readable [list [namespace current]::HandleData $s $sock]
02816 set ftp(SourceCI) $sock
02817 }
02818 }
02819 append -
02820 put {
02821 if {$ftp(inline)} {
02822 set ftp(Start_Time) -1
02823 set ftp(Total) 0
02824 fileevent $sock writable [list [namespace current]::HandleOutput $s $sock]
02825 } else {
02826 fileevent $sock writable [list [namespace current]::HandleData $s $sock]
02827 set ftp(DestCI) $sock
02828 }
02829 }
02830 default {
02831 error "Unknown state \"$ftp(State)\""
02832 }
02833 }
02834
02835 if { $VERBOSE } {
02836 DisplayMsg $s "D: Connection from $addr:$port" data
02837 }
02838 return
02839 }
02840
02841
02842
02843
02844
02845
02846
02847
02848
02849
02850
02851
02852
02853
02854 ret ::ftp::OpenActiveConn (type s ) {
02855 upvar ::ftp::ftp$s ftp
02856 variable VERBOSE
02857
02858 # Port address 0 is a dummy used to give the server the responsibility
02859 # of getting free new port addresses for every data transfer.
02860
02861 set rc [catch {set ftp(DummySock) [socket -server [list [namespace current]::InitDataConn $s] 0]} msg]
02862 if { $rc != 0 } {
02863 DisplayMsg $s "$msg" error
02864 return 0
02865 }
02866
02867 # get a new local port address for data transfer and convert it to a format
02868 # which is useable by the PORT command
02869
02870 set p [lindex [fconfigure $ftp(DummySock) -sockname] 2]
02871 if { $VERBOSE } {
02872 DisplayMsg $s "D: Port is $p" data
02873 }
02874 set ftp(DataPort) "[expr {$p / 256}],[expr {$p % 256}]"
02875
02876 return 1
02877 }
02878
02879
02880
02881
02882
02883
02884
02885
02886
02887
02888
02889
02890
02891
02892 ret ::ftp::OpenPassiveConn (type s , type buffer) {
02893 upvar ::ftp::ftp$s ftp
02894
02895 if { [regexp -- {([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)} $buffer all a1 a2 a3 a4 p1 p2] } {
02896 set ftp(LocalAddr) "$a1.$a2.$a3.$a4"
02897 set ftp(DataPort) "[expr {$p1 * 256 + $p2}]"
02898
02899 # establish data connection for passive mode
02900
02901 set rc [catch {set ftp(DataSock) [socket $ftp(LocalAddr) $ftp(DataPort)]} msg]
02902 if { $rc != 0 } {
02903 DisplayMsg $s "$msg" error
02904 return 0
02905 }
02906
02907 InitDataConn $s $ftp(DataSock) $ftp(LocalAddr) $ftp(DataPort)
02908 return 1
02909 } else {
02910 return 0
02911 }
02912 }
02913
02914
02915
02916
02917
02918
02919
02920
02921
02922
02923
02924
02925
02926
02927
02928 ret ::ftp::OpenControlConn (type s , optional block =1) {
02929 upvar ::ftp::ftp$s ftp
02930 variable DEBUG
02931 variable VERBOSE
02932
02933 # open a control channel
02934
02935 set rc [catch {set ftp(CtrlSock) [socket $ftp(RemoteHost) $ftp(Port)]} msg]
02936 if { $rc != 0 } {
02937 if { $VERBOSE } {
02938 DisplayMsg $s "C: No connection to server!" error
02939 }
02940 if { $DEBUG } {
02941 DisplayMsg $s "[list $msg]" error
02942 }
02943 unset ftp(State)
02944 return 0
02945 }
02946
02947 # configure control channel
02948
02949 fconfigure $ftp(CtrlSock) -buffering line -blocking $block -translation {auto crlf}
02950 fileevent $ftp(CtrlSock) readable [list [namespace current]::StateHandler $s $ftp(CtrlSock)]
02951
02952 # prepare local ip address for PORT command (convert pointed format
02953 # to comma format)
02954
02955 set ftp(LocalAddr) [lindex [fconfigure $ftp(CtrlSock) -sockname] 0]
02956 set ftp(LocalAddr) [string map {. ,} $ftp(LocalAddr)]
02957
02958 # report ready message
02959
02960 set peer [fconfigure $ftp(CtrlSock) -peername]
02961 if { $VERBOSE } {
02962 DisplayMsg $s "C: Connection from [lindex $peer 0]:[lindex $peer 2]" control
02963 }
02964
02965 return 1
02966 }
02967
02968
02969
02970
02971
02972
02973
02974
02975
02976
02977
02978
02979
02980 ret ::ftp::Command (type cb , type msg , type args) {
02981 if {[string length $cb]} {
02982 uplevel #0 $cb [list $msg] $args
02983 }
02984 }
02985
02986
02987
02988
02989
02990
02991 if { [string equal [uplevel "/* 0" {info commands tkcon}] "tkcon"] } {*/
02992
02993
02994 ret ::ftp::__ftp_ls (type args) {
02995 foreach i [eval ::ftp::List_org $args] {
02996 puts $i
02997 }
02998 }
02999
03000
03001 rename ::ftp::List ::ftp::List_org
03002
03003 alias ::ftp::List ::ftp::__ftp_ls
03004 alias bye catch {::ftp::Close; exit}
03005
03006 ::ftp = ::VERBOSE 1
03007 ::ftp = ::DEBUG 0
03008 }
03009
03010
03011
03012
03013 package provide ftp [lindex {Revision: 2.4.8} 1]
03014