ftp
stableEncode RFC 959 FTP command strings and parse FTP server responses. Use with a raw TCP socket to build an FTP client without external dependencies.
use plugin ftp Plugin snippets can be edited in the Playground, but native capabilities are executed locally with the Zolo CLI.
Functions (22)
- encode_user Encode a USER command string
- encode_pass Encode a PASS command string
- encode_list Encode a LIST command string
- encode_retr Encode a RETR (download) command string
- encode_stor Encode a STOR (upload) command string
- encode_cwd Encode a CWD (change directory) command
- encode_pwd Encode a PWD command string
- encode_quit Encode a QUIT command string
- encode_mkd Encode a MKD (make directory) command
- encode_rmd Encode a RMD (remove directory) command
- encode_dele Encode a DELE (delete file) command
- encode_type Encode a TYPE command (A or I)
- encode_pasv Encode a PASV command string
- encode_noop Encode a NOOP keepalive command
- encode_syst Encode a SYST command string
- encode_size Encode a SIZE command string
- encode_rnfr Encode a RNFR (rename from) command
- encode_rnto Encode a RNTO (rename to) command
- encode_cdup Encode a CDUP (parent directory) command
- parse_response Parse an FTP response line into code + message
- parse_list_entry Parse a Unix-style LIST directory entry
- parse_pasv_response Parse a PASV 227 response into host + port
From import to result
- 01Import only what you use
- 02Copy the smallest example
- 03Run it with the Zolo CLI
Overview¶
ftp is a stateless protocol-helper toolkit: it builds the RFC 959 command
strings an FTP client sends and parses the text lines an FTP server sends back.
It does not open sockets or transfer data itself — you supply the
networking (a raw TCP socket from the net/tcp layer) and use these functions
to translate between Zolo values and the wire format. Every encode_* function
returns a ready-to-send string that already includes the trailing \r\n, and
every parse_* function turns one server line into a plain table you can index
by field name.
The mental model is a request/response loop: encode a command, write it to the
socket, read a reply line, and feed that line to parse_response (or the
specialized parse_pasv_response / parse_list_entry) to interpret it. Reach
for this plugin when you need a dependency-free FTP client and want full control
over the connection.
Common patterns¶
Log in by encoding the USER/PASS handshake and reading each reply:
use plugin ftp::{encode_user, encode_pass, parse_response}
let user_cmd = encode_user("anonymous")
let pass_cmd = encode_pass("guest@example.com")
// write user_cmd and pass_cmd to your socket, then parse the replies:
let reply = parse_response("230 Login successful")
if reply["code"] == 230 {
print("logged in: {reply["message"]}")
}Enter passive mode, then open the data connection at the advertised address:
use plugin ftp::{encode_pasv, parse_pasv_response, encode_list}
let pasv = encode_pasv()
// send pasv, read the 227 reply, then decode where to connect:
let data = parse_pasv_response("227 Entering Passive Mode (192,168,1,1,19,136)")
print("open data socket to {data["host"]}:{data["port"]}")
print(encode_list("/pub"))Rename a remote file with the two-step RNFR / RNTO exchange:
use plugin ftp::{encode_rnfr, encode_rnto, parse_response}
print(encode_rnfr("/tmp/old.txt"))
let intermediate = parse_response("350 Ready for RNTO")
if intermediate["code"] == 350 {
print(encode_rnto("/tmp/new.txt"))
}Encode a USER command string
Returns the RFC 959 USER username\r\n command string ready to send over a TCP socket.
use plugin ftp::{encode_user, encode_pass}
let user_cmd = encode_user("anonymous")
let pass_cmd = encode_pass("guest@example.com")
print(user_cmd)
print(pass_cmd)Encode a PASS command string
Returns the PASS password\r\n command string.
use plugin ftp::{encode_pass}
let cmd = encode_pass("s3cr3t")
print(cmd) // PASS s3cr3t\r\nEncode a LIST command string
Returns LIST path\r\n, or just LIST\r\n when path is empty. Used to request a directory listing over the data connection.
use plugin ftp::{encode_list}
print(encode_list("/pub")) // LIST /pub\r\n
print(encode_list("")) // LIST\r\nEncode a RETR (download) command string
Returns RETR path\r\n to request a file download.
use plugin ftp::{encode_retr}
let cmd = encode_retr("/pub/readme.txt")
print(cmd)Encode a STOR (upload) command string
Returns STOR path\r\n to initiate a file upload.
use plugin ftp::{encode_stor}
let cmd = encode_stor("/uploads/data.csv")
print(cmd)Encode a CWD (change directory) command
Returns CWD path\r\n to change the working directory on the server.
use plugin ftp::{encode_cwd, encode_pwd}
let cwd = encode_cwd("/home/user")
let pwd = encode_pwd()
print(cwd)
print(pwd)Encode a PWD command string
Returns the bare PWD\r\n command to request the current working directory.
use plugin ftp::{encode_pwd}
print(encode_pwd()) // PWD\r\nEncode a QUIT command string
Returns QUIT\r\n to close the FTP session cleanly.
use plugin ftp::{encode_quit}
print(encode_quit()) // QUIT\r\nEncode a MKD (make directory) command
Returns MKD path\r\n to create a directory on the server.
use plugin ftp::{encode_mkd}
let cmd = encode_mkd("/backups/2026")
print(cmd)Encode a RMD (remove directory) command
Returns RMD path\r\n to remove a directory.
use plugin ftp::{encode_rmd}
print(encode_rmd("/tmp/old"))Encode a DELE (delete file) command
Returns DELE path\r\n to delete a file from the server.
use plugin ftp::{encode_dele}
print(encode_dele("/tmp/stale.log"))Encode a TYPE command (A or I)
Returns TYPE mode\r\n. Pass "I" for binary (image) mode or "A" for ASCII mode.
use plugin ftp::{encode_type}
print(encode_type("I")) // TYPE I\r\n (binary)
print(encode_type("A")) // TYPE A\r\n (ascii)Encode a PASV command string
Returns PASV\r\n to request passive mode. Parse the server reply with parse_pasv_response.
use plugin ftp::{encode_pasv}
print(encode_pasv()) // PASV\r\nEncode a NOOP keepalive command
Returns NOOP\r\n. Send periodically to keep an idle connection alive.
use plugin ftp::{encode_noop}
print(encode_noop()) // NOOP\r\nEncode a SYST command string
Returns SYST\r\n to query the server operating system type.
use plugin ftp::{encode_syst}
print(encode_syst()) // SYST\r\nEncode a SIZE command string
Returns SIZE path\r\n to query the byte size of a remote file.
use plugin ftp::{encode_size}
print(encode_size("/data/archive.tar.gz"))Encode a RNFR (rename from) command
Returns RNFR path\r\n, the first half of a rename. It names the file to be
renamed; the server replies 350 and then expects RNTO.
use plugin ftp::{encode_rnfr}
print(encode_rnfr("/tmp/old.txt")) // RNFR /tmp/old.txt\r\nEncode a RNTO (rename to) command
Returns RNTO path\r\n, the second half of a rename. Send it immediately after
encode_rnfr to give the file its new name.
use plugin ftp::{encode_rnfr, encode_rnto}
let from = encode_rnfr("/tmp/old.txt")
let to = encode_rnto("/tmp/new.txt")
print(from)
print(to)Encode a CDUP (parent directory) command
Returns CDUP\r\n to navigate to the parent directory.
use plugin ftp::{encode_cdup}
print(encode_cdup()) // CDUP\r\nParse an FTP response line into code + message
Parses a single FTP response line into {code, message, is_multiline}. code is the 3-digit integer. is_multiline is true when the code is followed by - (RFC 959 multi-line response).
use plugin ftp::{parse_response}
let r = parse_response("220 FTP server ready")
print(r["code"]) // 220
print(r["message"]) // FTP server ready
let m = parse_response("230-Login successful")
print(m["is_multiline"]) // trueBranch on the response code to decide what to do next:
use plugin ftp::{parse_response}
let r = parse_response("550 Requested action not taken")
if r["code"] >= 500 {
print("error: {r["message"]}")
} else {
print("ok: {r["message"]}")
}Parse a Unix-style LIST directory entry
Parses a single Unix-style LIST directory line into {permissions, size, date, name}.
use plugin ftp::{parse_list_entry}
let entry = parse_list_entry("-rw-r--r-- 1 user group 4096 Jan 1 12:00 readme.txt")
print(entry["name"]) // readme.txt
print(entry["size"]) // 4096Parse a PASV 227 response into host + port
Parses the 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) response into {host, port}. The port is computed as p1 * 256 + p2.
use plugin ftp::{encode_pasv, parse_pasv_response}
// After sending encode_pasv() and receiving the server reply:
let info = parse_pasv_response("227 Entering Passive Mode (192,168,1,1,19,136)")
print(info["host"]) // 192.168.1.1
print(info["port"]) // 5000