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:
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()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:
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().messageLoopback 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:
import "std:ftp"
auto attempt = ftp.connect("127.0.0.1:1", {timeout: 1000})
log attempt.isErr()
log attempt.unwrapErr().kindA 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:
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()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:
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()Missing remote files fail with a NotFoundError, so check the result and not just the call.
Rename, make folders, delete#
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()mkdir needs the parent to exist, and removeDir needs the folder to be empty.
You connect to a remote host with a username, a password and tls: "none". What happens?
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.
offline NetworkError
API reference#
60 members, generated from the std:ftp source of OSL 9.3.0.
Functions
ftp.changeDirectory(any path) booleanDeprecated. 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.FTPDeprecated. 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) booleanDeprecated. Deprecated: use client.mkdir(path) instead.
createDirectory always returns false.
ftp.currentDirectory() stringDeprecated. Deprecated: use client.cwd() instead.
currentDirectory always returns "".
ftp.delete(any path) booleanDeprecated. Deprecated: use client.remove(path) instead.
delete always returns false.
ftp.deleteDirectory(any path) booleanDeprecated. Deprecated: use client.removeDir(path) instead.
deleteDirectory always returns false.
ftp.disconnect() booleanDeprecated. Deprecated: use client.close() instead.
disconnect always returns false.
ftp.download(any remotePath, any localPath) booleanDeprecated. Deprecated: use client.download(remotePath, localPath) instead.
download always returns false.
ftp.downloadDirectory(any remoteDir, any localDir) booleanDeprecated. Deprecated: use client.downloadDir(remoteDir, localDir) instead.
downloadDirectory always returns false.
ftp.exists(any path) booleanDeprecated. Deprecated: use client.exists(path) instead.
exists always returns false.
ftp.getFileSize(any path) int64Deprecated. Deprecated: use client.size(path) instead.
getFileSize always returns 0.
ftp.getStatistics() objectDeprecated. Deprecated: use client.isConnected() instead.
getStatistics always returns {}.
ftp.isActive() booleanDeprecated. Deprecated: use client.isConnected() instead.
isActive always returns false.
ftp.list(any path) arrayDeprecated. Deprecated: use client.entries(path) instead.
list always returns [].
ftp.login() booleanDeprecated. 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) booleanDeprecated. 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) booleanDeprecated. 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) booleanDeprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.
sync always returns false.
ftp.upload(any localFile, any remotePath) booleanDeprecated. Deprecated: use client.upload(localPath, remotePath) instead.
upload always returns false.
ftp.uploadDirectory(any localDir, any remoteDir) booleanDeprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.
uploadDirectory always returns false.
*ftp.FTP
fTP.changeDirectory(any path) booleanDeprecated. 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.FTPDeprecated. Deprecated: use ftp.connect(address, {username, password}) instead.
connectEx connects to another server, or returns null on failure.
fTP.createDirectory(any path) booleanDeprecated. Deprecated: use client.mkdir(path) instead.
createDirectory creates a remote directory, returning false on failure.
fTP.currentDirectory() stringDeprecated. 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) booleanDeprecated. Deprecated: use client.remove(path) instead.
delete removes a remote file, returning false on failure.
fTP.deleteDirectory(any path) booleanDeprecated. Deprecated: use client.removeDir(path) instead.
deleteDirectory removes an empty remote directory, returning false on failure.
fTP.disconnect() booleanDeprecated. 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) booleanDeprecated. 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) booleanexists reports whether a remote file or directory exists at path.
fTP.getFileSize(any path) int64Deprecated. Deprecated: use client.size(path) instead.
getFileSize returns a remote file's size, or 0 on failure.
fTP.getStatistics() objectDeprecated. Deprecated: use client.isConnected() instead.
getStatistics returns {host, port, user, connected}, or {} when not connected.
fTP.isActive() booleanDeprecated. Deprecated: use client.isConnected() instead.
isActive reports whether the client is connected.
fTP.isConnected() booleanisConnected reports whether the client is logged in and not closed. It does not contact the server.
fTP.list(any path) arrayDeprecated. 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() booleanDeprecated. 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) booleanDeprecated. 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) booleanDeprecated. 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) booleanDeprecated. Deprecated: use client.uploadDir(localDir, remoteDir) instead.
uploadDirectory uploads a directory, returning false on failure.