Packages

ftp

Connect to an FTP server to list, upload, download, rename and delete remote files and folders.

import "std:ftp"

FTP is the old protocol for moving files to and from a server, still common on shared hosting and in data feeds. std:ftp connects, logs in, and gives you a remote file manager. For servers you control, prefer ssh with SFTP.

Connect#

ftp.connect(address, options) opens a connection and logs in. It returns a result. Without a username it logs in as anonymous. Close the connection when you are done:

main.osl
import "std:ftp"
import "std:env"

auto conn = ftp.connect("ftp.example.com", {
  username: "deploy",
  password: env.get("FTP_PASSWORD")
}).unwrap()

log conn.cwd().unwrap()
conn.close().unwrap()
Click the code to edit it

TLS is on by default (tls: "auto" switches to it when the server offers). "starttls" requires it, "implicit" speaks it from the first byte, and "none" never uses it.

Your password is never sent in the clear#

A login to another machine without TLS is refused before the password leaves your program. You can see this safely, because it fails before any data is sent:

main.osl
import "std:ftp"

auto attempt = ftp.connect("203.0.113.1:21", {
  username: "u",
  password: "p",
  tls: "none",
  timeout: 500
})
log attempt.unwrapErr().kind
log attempt.unwrapErr().message
Click the code to edit it

Loopback addresses are exempt, and so is the anonymous login. Change tls: "none" to tls: "auto" and run again: now it tries to connect, and fails with a NetworkError or TimeoutError because nothing is there.

Connection errors#

A refused or unreachable server is a NetworkError, and a slow one a TimeoutError:

main.osl
import "std:ftp"

auto attempt = ftp.connect("127.0.0.1:1", {timeout: 1000})
log attempt.isErr()
log attempt.unwrapErr().kind
Click the code to edit it

A refused login is a PermissionError.

Look around#

entries(path) lists a folder, stat describes one item, and exists, size and cwd answer smaller questions. Paths are the server's:

main.osl
import "std:ftp"

auto conn = ftp.connect("ftp.example.com", {username: "deploy", password: "..."}).unwrap()

for entry in conn.entries("/uploads").unwrap() (
  string kind = if entry.isDir then "dir " else "file"
  log kind entry.name entry.size
)
log conn.exists("/uploads/report.csv")
log conn.size("/uploads/report.csv").unwrapOr(-1)
conn.close().unwrap()
Click the code to edit it

Upload and download#

upload(local, remote) replaces the remote file, and download(remote, local) writes a local one. uploadDir and downloadDir copy a whole folder:

main.osl
import "std:ftp"
import "std:fs"

fs.writeFile("report.csv", "a,b\n1,2\n").unwrap()

auto conn = ftp.connect("ftp.example.com", {username: "deploy", password: "..."}).unwrap()
conn.upload("report.csv", "/uploads/report.csv").unwrap()
conn.download("/uploads/report.csv", "copy.csv").unwrap()
log fs.readFile("copy.csv").unwrap()
conn.close().unwrap()
Click the code to edit it

Missing remote files fail with a NotFoundError, so check the result and not just the call.

Rename, make folders, delete#

main.osl
import "std:ftp"

auto conn = ftp.connect("ftp.example.com", {username: "deploy", password: "..."}).unwrap()

conn.mkdir("/archive").unwrap()
conn.rename("/uploads/report.csv", "/archive/report.csv").unwrap()
conn.remove("/archive/old.csv").unwrap()
conn.removeDir("/empty-folder").unwrap()
conn.close().unwrap()
Click the code to edit it

mkdir needs the parent to exist, and removeDir needs the folder to be empty.

Quick check

You connect to a remote host with a username, a password and tls: "none". What happens?

Your turn

Handle a dead server

Try to connect to 127.0.0.1:1 with a 500 ms timeout. Print "offline" when it fails and the error kind after it.

main.osl
Ctrl Enter runs
Expected output
offline NetworkError

API reference#

60 members, generated from the std:ftp source of OSL 9.3.0.

Functions

#ftp.changeDirectory(any path) boolean

Deprecated. Deprecated: use client.chdir(path) instead.

changeDirectory always returns false.

#ftp.connect(string address, any ...options) result<*ftp.FTP, error>

connect opens and logs in to an FTP server. Options: username (default "anonymous"), password, timeout (milliseconds, default 10000) and tls, as in std:email: "auto" (the default) switches to TLS with AUTH TLS when the server offers it, "starttls" requires that, "implicit" speaks TLS from the start (port 990 unless address names one) and "none" never uses TLS. A password is never sent unencrypted to another machine: without TLS, a login to a host that isn't localhost or a loopback address fails with UnsupportedError before the password is sent, unless it is the anonymous login (user "anonymous" with no password or the default one). Also fails with PermissionError, NetworkError or TimeoutError. The deprecated four-argument form returns a disconnected client instead of failing.

#ftp.connectEx(any host, any port, any user, any ...rest) *ftp.FTP

Deprecated. Deprecated: use ftp.connect(address, {username, password}) instead, which fails with a typed error.

connectEx(host, port, user, password) connects and logs in, or returns null on any failure.

#ftp.createDirectory(any path) boolean

Deprecated. Deprecated: use client.mkdir(path) instead.

createDirectory always returns false.

#ftp.currentDirectory() string

Deprecated. Deprecated: use client.cwd() instead.

currentDirectory always returns "".

#ftp.delete(any path) boolean

Deprecated. Deprecated: use client.remove(path) instead.

delete always returns false.

#ftp.deleteDirectory(any path) boolean

Deprecated. Deprecated: use client.removeDir(path) instead.

deleteDirectory always returns false.

#ftp.disconnect() boolean

Deprecated. Deprecated: use client.close() instead.

disconnect always returns false.

#ftp.download(any remotePath, any localPath) boolean

Deprecated. Deprecated: use client.download(remotePath, localPath) instead.

download always returns false.

#ftp.downloadDirectory(any remoteDir, any localDir) boolean

Deprecated. Deprecated: use client.downloadDir(remoteDir, localDir) instead.

downloadDirectory always returns false.

#ftp.exists(any path) boolean

Deprecated. Deprecated: use client.exists(path) instead.

exists always returns false.

#ftp.getFileSize(any path) int64

Deprecated. Deprecated: use client.size(path) instead.

getFileSize always returns 0.

#ftp.getStatistics() object

Deprecated. Deprecated: use client.isConnected() instead.

getStatistics always returns {}.

#ftp.isActive() boolean

Deprecated. Deprecated: use client.isConnected() instead.

isActive always returns false.

#ftp.list(any path) array

Deprecated. Deprecated: use client.entries(path) instead.

list always returns [].

#ftp.login() boolean

Deprecated. Deprecated: use ftp.connect(address, options) instead.

login always returns false.

#ftp.passiveMode(any enabled) result<unit, error>

Deprecated. Deprecated: transfers always use passive mode.

passiveMode succeeds for true and fails with UnsupportedError for false.

#ftp.rename(any oldPath, any newPath) boolean

Deprecated. Deprecated: use client.rename(from, to) instead.

rename always returns false.

#ftp.setMode(any path, any mode) result<unit, error>

Deprecated. Deprecated: FTP has no portable way to change permissions.

setMode fails with UnsupportedError.

#ftp.setModificationTime(any path, any timestamp) boolean

Deprecated. Deprecated: use client.setModTime(path, unixMilliseconds) instead.

setModificationTime always returns false.

#ftp.setTimeout(any seconds) result<unit, error>

Deprecated. Deprecated: pass {timeout: milliseconds} to ftp.connect instead.

setTimeout fails with UnsupportedError.

#ftp.sync(any localDir, any remoteDir) boolean

Deprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.

sync always returns false.

#ftp.upload(any localFile, any remotePath) boolean

Deprecated. Deprecated: use client.upload(localPath, remotePath) instead.

upload always returns false.

#ftp.uploadDirectory(any localDir, any remoteDir) boolean

Deprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.

uploadDirectory always returns false.

*ftp.FTP

#fTP.changeDirectory(any path) boolean

Deprecated. Deprecated: use client.chdir(path) instead.

changeDirectory changes the remote working directory, returning false on failure.

#fTP.chdir(string path) result<unit, error>

chdir changes the remote working directory, which relative paths start from. Fails with NotFoundError when path doesn't exist.

#fTP.close() result<unit, error>

close logs out and closes the connection. Closing twice is a no-op. Fails with NetworkError when the server can't be told, but the connection is closed either way.

#fTP.connectEx(any host, any port, any user, any ...rest) *ftp.FTP

Deprecated. Deprecated: use ftp.connect(address, {username, password}) instead.

connectEx connects to another server, or returns null on failure.

#fTP.createDirectory(any path) boolean

Deprecated. Deprecated: use client.mkdir(path) instead.

createDirectory creates a remote directory, returning false on failure.

#fTP.currentDirectory() string

Deprecated. Deprecated: use client.cwd() instead.

currentDirectory returns the remote working directory, or "" on failure.

#fTP.cwd() result<string, error>

cwd returns the remote working directory, such as "/uploads".

#fTP.delete(any path) boolean

Deprecated. Deprecated: use client.remove(path) instead.

delete removes a remote file, returning false on failure.

#fTP.deleteDirectory(any path) boolean

Deprecated. Deprecated: use client.removeDir(path) instead.

deleteDirectory removes an empty remote directory, returning false on failure.

#fTP.disconnect() boolean

Deprecated. Deprecated: use client.close() instead.

disconnect logs out, returning false when the client wasn't connected.

#fTP.download(string remotePath, string localPath) result<unit, error>

download copies the remote file at remotePath to localPath, creating missing local directories. A partly written file is removed. Fails with NotFoundError when the remote file doesn't exist, IOError when the transfer breaks off, and PermissionError or IOError when localPath can't be written.

#fTP.downloadDir(string remoteDir, string localDir) result<unit, error>

downloadDir copies the remote directory remoteDir and everything in it to localDir. Fails like download, and with IOError when the server lists an unsafe name or a symlink.

#fTP.downloadDirectory(any remoteDir, any localDir) boolean

Deprecated. Deprecated: use client.downloadDir(remoteDir, localDir) instead.

downloadDirectory downloads a directory, returning false on failure.

#fTP.entries(string path) result<{isDir: boolean, isSymlink: boolean, modTime: int?, name: string, path: string, size: int, target: string?}[], error>

entries lists the directory at path ("" for the current directory). Each entry has the keys name, path, isDir, isSymlink, size (bytes), modTime (unix milliseconds, null when the server doesn't say) and target (where a symlink points, null otherwise). "." and ".." are left out. Fails with NotFoundError when the directory doesn't exist.

#fTP.exists(string path) boolean

exists reports whether a remote file or directory exists at path.

#fTP.getFileSize(any path) int64

Deprecated. Deprecated: use client.size(path) instead.

getFileSize returns a remote file's size, or 0 on failure.

#fTP.getStatistics() object

Deprecated. Deprecated: use client.isConnected() instead.

getStatistics returns {host, port, user, connected}, or {} when not connected.

#fTP.isActive() boolean

Deprecated. Deprecated: use client.isConnected() instead.

isActive reports whether the client is connected.

#fTP.isConnected() boolean

isConnected reports whether the client is logged in and not closed. It does not contact the server.

#fTP.list(any path) array

Deprecated. Deprecated: use client.entries(path) instead.

list returns the entries of a directory as {name, size, isDir, type, target, time}, or [] on failure.

#fTP.login() boolean

Deprecated. Deprecated: connect logs in; use client.isConnected() instead.

login checks that the connection is alive with a NOOP; connect has already logged in.

#fTP.mkdir(string path) result<unit, error>

mkdir creates the remote directory path. Its parent must exist. Fails with PermissionError when the server refuses, which most servers also do when path already exists.

#fTP.passiveMode(any enabled) result<unit, error>

Deprecated. Deprecated: transfers always use passive mode.

passiveMode succeeds for true, since transfers always use passive mode, and fails with UnsupportedError for false.

#fTP.remove(string path) result<unit, error>

remove deletes the remote file at path. Fails with NotFoundError when it doesn't exist.

#fTP.removeDir(string path, object? options) result<unit, error>

removeDir deletes the remote directory path, which must be empty. Options: recursive (boolean, default false) deletes everything in it first. Fails with PermissionError when the server refuses, which servers answer alike for a directory that is missing or not empty.

#fTP.rename(string from, string to) result<unit, error>

rename moves or renames the remote file or directory from to to. Fails with NotFoundError when from doesn't exist.

#fTP.setModTime(string path, int modTime) result<unit, error>

setModTime sets the modification time of the remote file at path to modTime (unix milliseconds; servers keep whole seconds). Fails with UnsupportedError when the server can't set times (no MFMT) and NotFoundError when the file doesn't exist.

#fTP.setMode(any path, any mode) result<unit, error>

Deprecated. Deprecated: FTP has no portable way to change permissions.

setMode never changed permissions; it now fails with UnsupportedError.

#fTP.setModificationTime(any path, any timestamp) boolean

Deprecated. Deprecated: use client.setModTime(path, unixMilliseconds) instead.

setModificationTime sets a remote file's time from an RFC 3339 string or unix seconds, returning false on failure.

#fTP.setTimeout(any seconds) result<unit, error>

Deprecated. Deprecated: pass {timeout: milliseconds} to ftp.connect instead.

setTimeout never changed anything once connected; it now says so.

#fTP.size(string path) result<int, error>

size returns the size in bytes of the remote file at path. Fails with NotFoundError when it doesn't exist.

#fTP.stat(string path) result<{isDir: boolean, isSymlink: boolean, modTime: int?, name: string, path: string, size: int, target: string?}, error>

stat describes the remote file or directory at path, with the same keys as an entries entry. Fails with NotFoundError when nothing is there and UnsupportedError when the server can't describe single files (no MLST).

#fTP.sync(any localDir, any remoteDir) boolean

Deprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.

sync uploads a directory, returning false on failure.

#fTP.upload(string localPath, string remotePath) result<unit, error>

upload copies the local file at localPath to remotePath, replacing it. Fails with NotFoundError or PermissionError when the local file can't be read, IOError when localPath isn't a regular file, and PermissionError when the server refuses the write.

#fTP.uploadDir(string localDir, string remoteDir) result<unit, error>

uploadDir copies the local directory localDir and everything in it to remoteDir, creating remote directories as needed (existing ones are reused). Fails like upload, and with IOError when localDir isn't a directory or contains a symlink.

#fTP.uploadDirectory(any localDir, any remoteDir) boolean

Deprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.

uploadDirectory uploads a directory, returning false on failure.