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::{encode_user, encode_pass, encode_list, …} 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
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\n
Encode 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\n
Encode 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\n
Encode a QUIT command string
Returns QUIT\r\n to close the FTP session cleanly.
use plugin ftp::{encode_quit}
print(encode_quit()) // QUIT\r\n
Encode 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\n
Encode 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\n
Encode 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\n
Encode 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\n
Encode 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\n
Parse 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"]) // true
Branch 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"]) // 4096
Parse 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