Attachment "stateHandler.tcl" to
ticket [1076923fff]
added by
keithv
2004-12-02 00:26:39.
#############################################################################
#
# StateHandler --
#
# Implements a finite state handler and a fileevent handler
# for the control channel
#
# Arguments:
# sock - socket name
# If called from a procedure than this argument is empty.
# If called from a fileevent than this argument contains
# the socket channel identifier.
proc ::ftp::StateHandler {s {sock ""}} {
upvar ::ftp::ftp$s ftp
variable DEBUG
variable VERBOSE
# disable fileevent on control socket, enable it at the and of the state machine
# fileevent $ftp(CtrlSock) readable {}
# there is no socket (and no channel to get) if called from a procedure
set rc " "
set msgtext {}
if { $sock != "" } {
set number 0 ;# Error condition
catch {set number [gets $sock bufline]}
if { $number > 0 } {
# get return code, check for multi-line text
if {![regexp -- "^-?(\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line msgtext]} {
set errmsg "C: Internal Error @ line 255.\
Regex pattern not matching the input \"$bufline\""
if {$VERBOSE} {
DisplayMsg $s $errmsg control
}
} else {
set buffer $bufline
# multi-line format detected ("-"), get all the lines
# until the real return code
while { [string equal $multi_line "-"] } {
set number [gets $sock bufline]
if { $number > 0 } {
append buffer \n "$bufline"
regexp -- "(^\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line
}
}
}
} elseif { [eof $ftp(CtrlSock)] } {
# remote server has closed control connection
# kill control socket, unset State to disable all following command
set rc 421
if { $VERBOSE } {
DisplayMsg $s "C: 421 Service not available, closing control connection." control
}
if {$ftp(State) ne "quit_sent"} {
set ftp(Error) "Service not available!"
}
CloseDataConn $s
WaitComplete $s 0
Command $ftp(Command) terminated
catch {unset ftp(State)}
catch {close $ftp(CtrlSock); unset ftp(CtrlSock)}
return
} else {
# Fix SF bug #466746: Incomplete line, do nothing.
return
}
}
if { $DEBUG } {
DisplayMsg $s "-> rc=\"$rc\"\n-> msgtext=\"$msgtext\"\n-> state=\"$ftp(State)\""
}
# In asynchronous mode, should we move on to the next state?
set nextState 0
# system status replay
if { [string equal $rc "211"] } {
return
}
# use only the first digit
regexp -- "^\[0-9\]?" $rc rc
switch -exact -- $ftp(State) {
user {
switch -exact -- $rc {
2 {
PutsCtrlSock $s "USER $ftp(User)"
set ftp(State) passwd
Command $ftp(Command) user
}
default {
set errmsg "Error connecting! $msgtext"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
passwd {
switch -exact -- $rc {
2 {
set complete_with 1
Command $ftp(Command) password
}
3 {
PutsCtrlSock $s "PASS $ftp(Passwd)"
set ftp(State) connect
Command $ftp(Command) password
}
default {
set errmsg "Error connecting! $msgtext"
set complete_with 0
Command $ftp(Command) error $msgtext
}
}
}
connect {
switch -exact -- $rc {
2 {
# The type is set after this, and we want to report
# that the connection is complete once the type is done
set nextState 1
if {[info exists ftp(NextState)] && ![llength $ftp(NextState)]} {
Command $ftp(Command) connect $s
} else {
set complete_with 1
}
}
default {
set errmsg "Error connecting! $msgtext"
set complete_with 0
Command $ftp(Command) error $msgtext
}
}
}
connect_last {
Command $ftp(Command) connect $s
set complete_with 1
}
quit {
PutsCtrlSock $s "QUIT"
set ftp(State) quit_sent
}
quit_sent {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) quit
}
default {
set errmsg "Error disconnecting! $msgtext"
set complete_with 0
Command $ftp(Command) error $msgtext
}
}
}
quote {
PutsCtrlSock $s $ftp(Cmd)
set ftp(State) quote_sent
}
quote_sent {
set complete_with 1
set ftp(Quote) $buffer
set nextState 1
Command $ftp(Command) quote $buffer
}
type {
if { [string equal $ftp(Type) "ascii"] } {
PutsCtrlSock $s "TYPE A"
} elseif { [string equal $ftp(Type) "binary"] } {
PutsCtrlSock $s "TYPE I"
} else {
PutsCtrlSock $s "TYPE L"
}
set ftp(State) type_sent
}
type_sent {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) type $ftp(Type)
}
default {
set errmsg "Error setting type \"$ftp(Type)\"!"
set complete_with 0
Command $ftp(Command) error "error setting type \"$ftp(Type)\""
}
}
}
type_change {
set ftp(Type) $ftp(type:changeto)
set ftp(State) type
StateHandler $s
}
nlist_active {
if { [OpenActiveConn $s] } {
PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
set ftp(State) nlist_open
} else {
set errmsg "Error setting port!"
}
}
nlist_passive {
PutsCtrlSock $s "PASV"
set ftp(State) nlist_open
}
nlist_open {
switch -exact -- $rc {
1 {}
2 {
if { [string equal $ftp(Mode) "passive"] } {
if { ![OpenPassiveConn $s $buffer] } {
set errmsg "Error setting PASSIVE mode!"
set complete_with 0
Command $ftp(Command) error "error setting passive mode"
}
}
PutsCtrlSock $s "NLST$ftp(Dir)"
set ftp(State) list_sent
}
default {
if { [string equal $ftp(Mode) "passive"] } {
set errmsg "Error setting PASSIVE mode!"
} else {
set errmsg "Error setting port!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
list_active {
if { [OpenActiveConn $s] } {
PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
set ftp(State) list_open
} else {
set errmsg "Error setting port!"
Command $ftp(Command) error $errmsg
}
}
list_passive {
PutsCtrlSock $s "PASV"
set ftp(State) list_open
}
list_open {
switch -exact -- $rc {
1 {}
2 {
if { [string equal $ftp(Mode) "passive"] } {
if { ![OpenPassiveConn $s $buffer] } {
set errmsg "Error setting PASSIVE mode!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
PutsCtrlSock $s "LIST$ftp(Dir)"
set ftp(State) list_sent
}
default {
if { [string equal $ftp(Mode) "passive"] } {
set errmsg "Error setting PASSIVE mode!"
} else {
set errmsg "Error setting port!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
list_sent {
switch -exact -- $rc {
1 -
2 {
set ftp(State) list_close
}
default {
if { [string equal $ftp(Mode) "passive"] } {
unset ftp(state.data)
}
set errmsg "Error getting directory listing!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
list_close {
switch -exact -- $rc {
1 {}
2 {
set nextState 1
if {[info exists ftp(NextState)] && ![llength $ftp(NextState)]} {
Command $ftp(Command) list [ListPostProcess $ftp(List)]
} else {
set complete_with 1
}
}
default {
set errmsg "Error receiving list!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
list_last {
Command $ftp(Command) list [ListPostProcess $ftp(List)]
set complete_with 1
}
size {
PutsCtrlSock $s "SIZE $ftp(File)"
set ftp(State) size_sent
}
size_sent {
switch -exact -- $rc {
2 {
regexp -- "^\[0-9\]+ (.*)$" $buffer all ftp(FileSize)
set complete_with 1
set nextState 1
Command $ftp(Command) size $ftp(File) $ftp(FileSize)
}
default {
set errmsg "Error getting file size!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
modtime {
if {$ftp(DateTime) != ""} {
PutsCtrlSock $s "MDTM $ftp(DateTime) $ftp(File)"
} else { ;# No DateTime Specified
PutsCtrlSock $s "MDTM $ftp(File)"
}
set ftp(State) modtime_sent
}
modtime_sent {
switch -exact -- $rc {
2 {
regexp -- "^\[0-9\]+ (.*)$" $buffer all ftp(DateTime)
set complete_with 1
set nextState 1
Command $ftp(Command) modtime $ftp(File) [ModTimePostProcess $ftp(DateTime)]
}
default {
if {$ftp(DateTime) != ""} {
set errmsg "Error setting modification time! No server MDTM support?"
} else {
set errmsg "Error getting modification time!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
pwd {
PutsCtrlSock $s "PWD"
set ftp(State) pwd_sent
}
pwd_sent {
switch -exact -- $rc {
2 {
regexp -- "^.*\"(.*)\"" $buffer temp ftp(Dir)
set complete_with 1
set nextState 1
Command $ftp(Command) pwd $ftp(Dir)
}
default {
set errmsg "Error getting working dir!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
cd {
PutsCtrlSock $s "CWD$ftp(Dir)"
set ftp(State) cd_sent
}
cd_sent {
switch -exact -- $rc {
1 {}
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) cd $ftp(Dir)
}
default {
set errmsg "Error changing directory to \"$ftp(Dir)\""
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
mkdir {
PutsCtrlSock $s "MKD $ftp(Dir)"
set ftp(State) mkdir_sent
}
mkdir_sent {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) mkdir $ftp(Dir)
}
default {
set errmsg "Error making dir \"$ftp(Dir)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
rmdir {
PutsCtrlSock $s "RMD $ftp(Dir)"
set ftp(State) rmdir_sent
}
rmdir_sent {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) rmdir $ftp(Dir)
}
default {
set errmsg "Error removing directory!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
delete {
PutsCtrlSock $s "DELE $ftp(File)"
set ftp(State) delete_sent
}
delete_sent {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) delete $ftp(File)
}
default {
set errmsg "Error deleting file \"$ftp(File)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
rename {
PutsCtrlSock $s "RNFR $ftp(RenameFrom)"
set ftp(State) rename_to
}
rename_to {
switch -exact -- $rc {
3 {
PutsCtrlSock $s "RNTO $ftp(RenameTo)"
set ftp(State) rename_sent
}
default {
set errmsg "Error renaming file \"$ftp(RenameFrom)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
rename_sent {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) rename $ftp(RenameFrom) $ftp(RenameTo)
}
default {
set errmsg "Error renaming file \"$ftp(RenameFrom)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
put_active {
if { [OpenActiveConn $s] } {
PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
set ftp(State) put_open
} else {
set errmsg "Error setting port!"
Command $ftp(Command) error $errmsg
}
}
put_passive {
PutsCtrlSock $s "PASV"
set ftp(State) put_open
}
put_open {
switch -exact -- $rc {
1 -
2 {
if { [string equal $ftp(Mode) "passive"] } {
if { ![OpenPassiveConn $s $buffer] } {
set errmsg "Error setting PASSIVE mode!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
PutsCtrlSock $s "STOR $ftp(RemoteFilename)"
set ftp(State) put_sent
}
default {
if { [string equal $ftp(Mode) "passive"] } {
set errmsg "Error setting PASSIVE mode!"
} else {
set errmsg "Error setting port!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
put_sent {
switch -exact -- $rc {
1 -
2 {
set ftp(State) put_close
}
default {
if { [string equal $ftp(Mode) "passive"] } {
# close already opened DataConnection
unset ftp(state.data)
}
set errmsg "Error opening connection!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
put_close {
switch -exact -- $rc {
1 {
# Keep going
return
}
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) put $ftp(RemoteFilename)
}
default {
DisplayMsg $s "rc = $rc msgtext = \"$msgtext\""
set errmsg "Error storing file \"$ftp(RemoteFilename)\" due to \"$msgtext\""
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
append_active {
if { [OpenActiveConn $s] } {
PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
set ftp(State) append_open
} else {
set errmsg "Error setting port!"
Command $ftp(Command) error $errmsg
}
}
append_passive {
PutsCtrlSock $s "PASV"
set ftp(State) append_open
}
append_open {
switch -exact -- $rc {
1 -
2 {
if { [string equal $ftp(Mode) "passive"] } {
if { ![OpenPassiveConn $s $buffer] } {
set errmsg "Error setting PASSIVE mode!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
PutsCtrlSock $s "APPE $ftp(RemoteFilename)"
set ftp(State) append_sent
}
default {
if { [string equal $ftp(Mode) "passive"] } {
set errmsg "Error setting PASSIVE mode!"
} else {
set errmsg "Error setting port!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
append_sent {
switch -exact -- $rc {
1 {
set ftp(State) append_close
}
default {
if { [string equal $ftp(Mode) "passive"] } {
# close already opened DataConnection
unset ftp(state.data)
}
set errmsg "Error opening connection!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
append_close {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) append $ftp(RemoteFilename)
}
default {
set errmsg "Error storing file \"$ftp(RemoteFilename)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
reget_active {
if { [OpenActiveConn $s] } {
PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
set ftp(State) reget_restart
} else {
set errmsg "Error setting port!"
Command $ftp(Command) error $errmsg
}
}
reget_passive {
PutsCtrlSock $s "PASV"
set ftp(State) reget_restart
}
reget_restart {
switch -exact -- $rc {
2 {
if { [string equal $ftp(Mode) "passive"] } {
if { ![OpenPassiveConn $s $buffer] } {
set errmsg "Error setting PASSIVE mode!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
if { $ftp(FileSize) != 0 } {
PutsCtrlSock $s "REST $ftp(FileSize)"
set ftp(State) reget_open
} else {
PutsCtrlSock $s "RETR $ftp(RemoteFilename)"
set ftp(State) reget_sent
}
}
default {
set errmsg "Error restarting filetransfer of \"$ftp(RemoteFilename)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
reget_open {
switch -exact -- $rc {
2 -
3 {
PutsCtrlSock $s "RETR $ftp(RemoteFilename)"
set ftp(State) reget_sent
}
default {
if { [string equal $ftp(Mode) "passive"] } {
set errmsg "Error setting PASSIVE mode!"
} else {
set errmsg "Error setting port!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
reget_sent {
switch -exact -- $rc {
1 {
set ftp(State) reget_close
}
default {
if { [string equal $ftp(Mode) "passive"] } {
# close already opened DataConnection
unset ftp(state.data)
}
set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
reget_close {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
Command $ftp(Command) get $ftp(RemoteFilename):$ftp(From):$ftp(To)
unset ftp(From) ftp(To)
}
default {
set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
get_active {
if { [OpenActiveConn $s] } {
PutsCtrlSock $s "PORT $ftp(LocalAddr),$ftp(DataPort)"
set ftp(State) get_open
} else {
set errmsg "Error setting port!"
Command $ftp(Command) error $errmsg
}
}
get_passive {
PutsCtrlSock $s "PASV"
set ftp(State) get_open
}
get_open {
switch -exact -- $rc {
1 -
2 -
3 {
if { [string equal $ftp(Mode) "passive"] } {
if { ![OpenPassiveConn $s $buffer] } {
set errmsg "Error setting PASSIVE mode!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
PutsCtrlSock $s "RETR $ftp(RemoteFilename)"
set ftp(State) get_sent
}
default {
if { [string equal $ftp(Mode) "passive"] } {
set errmsg "Error setting PASSIVE mode!"
} else {
set errmsg "Error setting port!"
}
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
get_sent {
switch -exact -- $rc {
1 {
set ftp(State) get_close
}
default {
if { [string equal $ftp(Mode) "passive"] } {
# close already opened DataConnection
unset ftp(state.data)
}
set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
get_close {
switch -exact -- $rc {
2 {
set complete_with 1
set nextState 1
if {$ftp(inline)} {
upvar #0 $ftp(get:varname) returnData
set returnData $ftp(GetData)
Command $ftp(Command) get $ftp(GetData)
} else {
Command $ftp(Command) get $ftp(RemoteFilename)
}
}
default {
set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
set complete_with 0
Command $ftp(Command) error $errmsg
}
}
}
default {
error "Unknown state \"$ftp(State)\""
}
}
# finish waiting
if { [info exists complete_with] } {
WaitComplete $s $complete_with
}
# display control channel message
if { [info exists buffer] } {
if { $VERBOSE } {
foreach line [split $buffer \n] {
DisplayMsg $s "C: $line" control
}
}
}
# Rather than throwing an error in the event loop, set the ftp(Error)
# variable to hold the message so that it can later be thrown after the
# the StateHandler has completed.
if { [info exists errmsg] } {
set ftp(Error) $errmsg
}
# If operating asynchronously, commence next state
if {$nextState && [info exists ftp(NextState)] && [llength $ftp(NextState)]} {
# Pop the head of the NextState queue
set ftp(State) [lindex $ftp(NextState) 0]
set ftp(NextState) [lreplace $ftp(NextState) 0 0]
StateHandler $s
}
# enable fileevent on control socket again
#fileevent $ftp(CtrlSock) readable [list ::ftp::StateHandler $ftp(CtrlSock)]
}