px普贤教程PuXian
普贤教程 › 生态包

FTP / IMAP / POP3

ftp、imap、pop3 三个明文协议客户端。它们的写法高度一致:connect → login → 若干命令 → quit,每步返 Result。三个包都不支持 TLS。

先说最大的限制:没有 TLS
三个包里 TLS 相关代码行数为 0。所以:ftps(990)、隐式 imaps(993)、pop3s(995)、 以及 STARTTLS 全部用不了。它们只适合内网、本机、或明确用明文的老设备。
公网邮件请走 smtp(那个包有 TLS)或用 HTTP API;公网 FTP 现在几乎都强制 FTPS,实际也接不上。

ftp

px
import ftp

var c = ft_connect("127.0.0.1", 21)          # 可选第三参 {"timeout_ms": 5000}
print(c.is_ok(), c.err())
text
false ftp: connect 127.0.0.1:21 failed: net: 连接 127.0.0.1:21 失败 (111)

连上后(Ok({fd, host, port}))依次:

px
var c = ft_connect("host", 21, {"timeout_ms": 5000}).ok()
ft_login(c, "user", "pass")        # USER/PASS,返 Result
ft_type(c, "I")                    # 二进制模式,传文件前必做
ft_pwd(c)                          # 当前目录
ft_cwd(c, "/upload")               # 切目录
ft_list(c)                         # 目录列表(走 PASV 数据连接)
ft_retr(c, "/path/file.txt")       # 下载 → Ok(bytes)
ft_stor(c, "/path/new.txt", data)  # 上传,data 是 bytes
ft_mkdir(c, "/upload/2026"); ft_rmdir(c, "/upload/old")
ft_delete(c, "/path/gone.txt")
ft_quit(c)

底层还暴露了 ft_send / ft_read_reply / ft_expect / ft_pasv_open,遇到包没封装的命令(SITE、MLSD)可以 ft_cmd(c, "SITE CHMOD 644 x", [200, 250]) 自己发。

pop3

px
import pop3

var c = po_connect("127.0.0.1", 110)     # 可选 {"timeout_ms": …}
print(c.is_ok(), c.err())
text
false pop3: connect 127.0.0.1:110 failed: net: 连接 127.0.0.1:110 失败 (111)

连上后:

px
var c = po_connect("host", 110, {"timeout_ms": 5000}).ok()
po_login(c, "user", "pass")        # USER/PASS
po_stat(c)                         # Ok({count, size})
po_list(c)                         # 每封的大小
po_retr(c, 1)                      # 整封原文(含头与体)→ 再丢给 mailparse 解析
po_top(c, 1, 10)                   # 只要头部 + 前 10 行正文
po_uidl(c)                         # 唯一 ID,做增量收信靠它
po_dele(c, 1)                      # 标记删除(QUIT 时才真正生效)
po_rset(c)                         # 撤销所有删除标记
po_noop(c); po_capa(c); po_quit(c)
POP3 的删除是「提交式」的
po_dele 只打标记,po_quit(c) 正常退出才落地;中途断线或 po_rset 就全部回滚。这跟直觉相反,但正是协议本身的设计。

imap

px
import imap

var c = imap_connect("127.0.0.1", 143, 1000)   # 第三个参数是超时毫秒,必填
print(c.is_ok(), c.err())
text
false imap: 连接失败 127.0.0.1:143 net: 连接 127.0.0.1:143 失败 (111)

IMAP 是「选邮箱再取信」的模型:

px
var c = imap_connect("host", 143, 5000).ok()
imap_login(c, "user", "pass")
print(str(imap_list(c)))            # 邮箱列表
imap_select(c, "INBOX")             # 只读用 imap_examine
print(str(imap_search(c, "UNSEEN")))     # 未读邮件序号
print(str(imap_fetch(c, "1", "(BODY[])")))
imap_store(c, "1", "+FLAGS (\\Seen)")    # 标已读
imap_create(c, "Archive"); imap_copy(c, "1", "Archive")
imap_close(c); imap_logout(c)
返回形态不一致
imap_connect 返 Ok(conn);imap_search / imap_fetch / imap_list 返 Ok(...);但 imap_login / imap_select / imap_store 等命令类只返 Ok(true)。写代码时别假设都能拿到数据。

这三个包本轮的验证边界

诚实交代:本机没有任何 FTP / IMAP / POP3 服务,我只验证了「连不上时的行为」——三者都干净地返回 Err (含协议名、主机、端口、系统 errno 111),没有崩、没有半开连接残留。成功路径(登录、列目录、取信)我没有跑过, 所以本页示例里的命令顺序来自源码与协议规范,不是实测输出。

要自己验:本机起一个 vsftpd(或 pyftpdlib 之类)就能把 ftp 全链路走通;IMAP/POP3 可以用 Dovecot 起一个本地实例。别拿生产邮箱当练习场——po_dele 是真删。