vfs::ftp

Part of vfslib, this vfs ftp package allows one to access directories and files via the ftp protocol.


dbohdan 2014-06-02: Currently vfs::ftp does not support passive mode FTP connections. If you try to use it and your firewall isn't configured to let the FTP server's connections back through, which is necessary for the active mode, it can lead to frustrating errors. Code like

if {[catch {set fp [open "ftp://example.com/pub/README" r]} err]} {puts "$err" }

will fail with the cryptic error message can't read "tmp": no such variable.

Luckily, this is easy to fix. There is an open ticket about this issue at http://sourceforge.net/p/tclvfs/bugs/86/ that suggests a fix but it has not been merged yet. To fix the issue for your own code (likely a better solution if you expect your users to have tclVFS installed from a standard repository) do the following:

Where you have

package require vfs::urltype
vfs::urltype::Mount ftp

replace it with

package require vfs::urltype
vfs::urltype::Mount ftp

# Replace vfs::ftp::Mount to enable vfs::ftp to work in passive
# mode and make that the default.
package require vfs::ftp
proc vfs::ftp::Mount {dirurl local {mode passive}} {
    set dirurl [string trim $dirurl]
    ::vfs::log "ftp-vfs: attempt to mount $dirurl at $local"
    if {[string index $dirurl end] != "/"} {
        ::vfs::log "ftp-vfs: adding missing directory delimiter to mount point"
        append dirurl "/"
    }

    set urlRE {(?:ftp://)?(?:([^@:]*)(?::([^@]*))?@)?([^/:]+)(?::([0-9]*))?/(.*/)?$}
    if {![regexp $urlRE $dirurl - user pass host port path]} {
        return -code error "Sorry I didn't understand\
          the url address \"$dirurl\""
    }

    if {![string length $user]} {
        set user anonymous
    }

    if {![string length $port]} {
        set port 21
    }

    set fd [::ftp::Open $host $user $pass -port $port -output ::vfs::ftp::log -mode $mode]
    if {$fd == -1} {
        error "Mount failed"
    }

    if {$path != ""} {
        if {[catch {
            ::ftp::Cd $fd $path
        } err]} {
            ftp::Close $fd
            error "Opened ftp connection, but then received error: $err"
        }
    }

    if {![catch {vfs::filesystem info $dirurl}]} {
        # unmount old mount
        ::vfs::log "ftp-vfs: unmounted old mount point at $dirurl"
        vfs::unmount $dirurl
    }
    ::vfs::log "ftp $host, $path mounted at $fd"
    vfs::filesystem mount $local [list vfs::ftp::handler $fd $path]
    # Register command to unmount
    vfs::RegisterMount $local [list ::vfs::ftp::Unmount $fd]
    return $fd
}