# @electerm/ftp-srv 中文文档

规范地址：https://npmcn.com/package/%40electerm/ftp-srv/

版本：1\.1\.0
npm 元数据更新时间：2026-10-29T13:54:48.914Z
中文译文更新时间：2026-11-13T10:55:13.541Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40electerm/ftp-srv
原文对照：https://npmcn.com/package/%40electerm/ftp-srv/?lang=original
项目主页：https://github.com/electerm/ftp-srv#readme
源码仓库：https://github.com/electerm/ftp-srv

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：224 kB，186 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=16
- 运行依赖：1 个
- 首次发布：2026-04-25T00:29:34.494Z
- 最新版本发布：2026-10-29T13:54:48.431Z
- 近 30 天下载量：14135（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：11（近 12 个月 11 个）

## 中文简介

现代可扩展的 FTP 服务器( TypeScript 版本)

## 安装

```sh
npm install @electerm/ftp-srv
```

也可以使用：`pnpm add @electerm/ftp-srv`、`yarn add @electerm/ftp-srv`、`bun add @electerm/ftp-srv`

## README

# @electerm/ftp-srv

[![English](https://img.shields.io/badge/lang-en-blue.svg)](https://npmcn.com/package/%40electerm/ftp-srv/README.md) [![中文](https://img.shields.io/badge/lang-%E4%B8%AD%E6%96%87-orange.svg)](https://npmcn.com/package/%40electerm/ftp-srv/README.zh.md)

现代、可扩展的 FTP 服务器（TypeScript 版本）。

本项目是 [ftp-srv](https://github.com/QuorumDMS/ftp-srv) 的 TypeScript 重构版本，同时发布 ESM 和 CJS 格式——并修复了原版中的安全漏洞。API 与原版 `ftp-srv` 向后兼容。

原始实现的全部功劳归于原作者 [Tyler Stewart](https://github.com/QuorumDMS/ftp-srv)。详见 [CREDITS](https://npmcn.com/package/%40electerm/ftp-srv/CREDITS) 文件。

## 安装

```bash
npm install @electerm/ftp-srv
```

## 使用

```js
// ESM
import FtpServer from '@electerm/ftp-srv';

// CJS
const { FtpServer } = require('@electerm/ftp-srv');

// 向后兼容别名（兼容原始 ftp-srv 包）
const { FtpSrv } = require('@electerm/ftp-srv');
```

### 基本示例

```js
import FtpServer from '@electerm/ftp-srv';

const server = new FtpServer({
  url: 'ftp://0.0.0.0:21',
  anonymous: false,
});

server.on('login', ({ connection, username, password }, resolve, reject) => {
  if (username === 'admin' && password === 'secret') {
    return resolve({ root: '/srv/ftp' });
  }
  return reject(new Error('用户名或密码无效'));
});

await server.listen();
console.log('FTP 服务器已在 21 端口启动');

// 优雅关闭
await server.close();
```

### 按用户设置根目录

```js
const server = new FtpServer({
  url: 'ftp://0.0.0.0:2121',
  pasv_min: 10000,
  pasv_max: 10100,
  anonymous: false,
});

server.on('login', ({ username, password }, resolve, reject) => {
  if (username === 'alice' && password === 'pass') {
    return resolve({
      root: '/home/alice',
      cwd: '/uploads',  // 初始工作目录（相对于 root）
    });
  }
  return reject(new Error('凭据无效'));
});

await server.listen();
```

### 匿名访问

```js
const server = new FtpServer({
  url: 'ftp://0.0.0.0:21',
  anonymous: true,   // 接受任意用户名/密码
  root: '/srv/ftp',  // 所有连接的默认根目录
});

server.on('login', (_, resolve) => resolve({}));

await server.listen();
```

### TLS（FTPS）

```js
import fs from 'fs';

const server = new FtpServer({
  url: 'ftps://0.0.0.0:990',
  tls: {
    key: fs.readFileSync('server.key'),
    cert: fs.readFileSync('server.crt'),
  },
});
```

配置了 `tls` 后两种方式都支持：

-   **隐式 FTPS** —— `url: 'ftps://...'`，控制连接从第一个字节起就是 TLS。
-   **显式 FTPS（FTPES）** —— `url: 'ftp://...'`，客户端发 `AUTH TLS` 后控制连接就地升级（RFC 4217）， 传输前再 `PBSZ 0` / `PROT P`。数据连接同样加密。`FEAT` 只在配置了 `tls` 时才通告 `AUTH TLS` / `PBSZ` / `PROT`；没配 `tls` 时直接回 `502`，不会假装升级成功。

## 安全说明

-   **FTP bounce 防护** —— 只有当 `PORT` / `EPRT` 给出的地址就是客户端自己（控制连接的对端地址）时才接受； 被动数据连接来自其他主机时一律回 `550` 并断开。两个命令都要求已登录。
-   **被动端口会释放** —— 重复发送 `PASV` / `EPSV` 会先关闭上一个监听，空闲 30s 的被动监听也会自动关闭， 客户端连进来却不传输也吃不满端口段。
-   **数据通道加密** —— 配置 `tls` 后，被动/主动数据连接同样是 TLS，不只是控制连接。

## API

### `new FtpServer(options)`

| 选项 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `url` | `string` | `'ftp://127.0.0.1:21'` | 监听的协议、主机名和端口。使用 `ftp://` 或 `ftps://`。 |
| `root` | `string` | — | 所有连接的默认根目录（当 `login` 的 resolve 未指定时使用）。 |
| `pasv_url` | `string | (clientIp: string) => string` | 自动检测 | 被动连接时向客户端通告的 IP。省略时优先使用控制连接的本机地址（客户端实际连进来的地址），再回落到绑定地址 / 与客户端同网段的网卡地址。 |
| `pasv_min` | `number` | `1024` | 被动端口范围起始值。 |
| `pasv_max` | `number` | `65535` | 被动端口范围结束值。 |
| `anonymous` | `boolean` | `false` | 允许匿名登录（接受任意用户名/密码）。 |
| `blacklist` | `string[]` | `[]` | 全局拒绝的 FTP 命令列表。 |
| `whitelist` | `string[]` | `[]` | 全局允许的 FTP 命令列表（其余均拒绝）。 |
| `greeting` | `string | string[]` | — | 客户端连接时发送的欢迎消息。 |
| `tls` | `object` | — | Node.js TLS 选项（`key`、`cert` 等），用于 `ftps://` 连接。 |
| `file_format` | `'ls' | 'ep' | function` | `'ls'` | LIST 响应格式。`'ls'` = Unix 风格，`'ep'` = EPLF，或自定义 `(stat) => string` 函数。 |
| `log` | `Logger` | bunyan logger | 兼容 bunyan 的日志对象（`trace`、`debug`、`info`、`warn`、`error`、`fatal`、`child`）。 |
| `timeout` | `number` | `0` | 空闲 socket 超时时间（毫秒）。`0` 表示禁用。 |
| `endOnProcessSignal` | `boolean` | `true` | 收到 `SIGTERM`/`SIGINT`/`SIGQUIT` 时调用 `close()` 并退出进程。 |

### `server.listen()` → `Promise<string>`

启动服务器并开始接受连接。

### `server.close()` → `Promise<void>`

优雅关闭所有连接并停止服务器。

## 事件

### `login`

客户端尝试登录时触发。**必须**监听此事件并调用 `resolve` 或 `reject`。

```js
server.on('login', ({ connection, username, password }, resolve, reject) => {
  // 使用每用户选项 resolve：
  resolve({
    root: '/srv/ftp/alice',  // 本次会话的根目录
    cwd: '/',                // 初始工作目录（相对于 root）
    fs: customFsInstance,    // 自定义 FileSystem 实例（可选）
    blacklist: ['DELE'],     // 每连接命令黑名单
    whitelist: [],           // 每连接命令白名单
  });

  // 或拒绝登录：
  reject(new Error('凭据无效'));
});
```

### `connect`

新客户端 socket 连接时触发（登录前）。

```js
server.on('connect', ({ connection, id, newConnectionCount }) => {});
```

### `disconnect`

客户端断开连接时触发。

```js
server.on('disconnect', ({ connection, id, newConnectionCount }) => {});
```

### `client-error`

客户端连接发生错误时触发。

```js
server.on('client-error', ({ connection, context, error }) => {});
```

### `server-error`

底层 TCP 服务器发生错误时触发。

```js
server.on('server-error', ({ error }) => {});
```

### `closing` / `closed`

调用 `close()` 时及关闭完成后触发。

```js
server.on('closing', () => {});
server.on('closed', () => {});
```

## 自定义文件系统

继承 `FileSystem` 可覆盖服务器读写文件的方式。

```js
import { FileSystem } from '@electerm/ftp-srv';

class MyFileSystem extends FileSystem {
  async list(dirPath = '.') {
    // 返回带有 name 属性的 fs.Stats 类对象数组
    const entries = await super.list(dirPath);
    return entries.filter(e => !e.name.startsWith('.'));  // 隐藏点文件
  }

  async get(fileName) {
    return super.get(fileName);
  }
}

server.on('login', (_, resolve) => {
  resolve({ fs: new MyFileSystem(connection, { root: '/srv/ftp' }) });
});
```

可覆盖的方法：

| 方法 | 说明 |
| --- | --- |
| `currentDirectory()` | 返回当前工作目录路径。 |
| `get(fileName)` | 返回带有 `name` 属性的 `fs.Stats` 类对象。 |
| `list(dirPath)` | 返回带有 `name` 属性的 `fs.Stats` 类对象数组。 |
| `chdir(dirPath)` | 切换工作目录，返回新路径。 |
| `write(fileName, options)` | 返回 `{ stream, clientPath }` 用于写入。 |
| `read(fileName, options)` | 返回 `{ stream, clientPath }` 用于读取。 |
| `delete(fileName)` | 删除文件。 |
| `mkdir(dirPath)` | 创建目录。 |
| `rename(from, to)` | 重命名文件或目录。 |
| `chmod(fileName, mode)` | 修改文件权限。 |
| `getUniqueName(fileName)` | 返回唯一文件名（用于 STOU 命令）。 |

## 导出

```js
import FtpServer, {
  FtpServer,       // 具名导出（同一个类）
  FtpSrv,          // 向后兼容别名，兼容原始 ftp-srv 包
  FileSystem,
  FtpConnection,
  errors,
} from '@electerm/ftp-srv';
```

## 命令行

```bash
npx electermftpsrv ftp://0.0.0.0:9876 --root ~/Documents
```

## License

MIT

## 常见问题

### @electerm/ftp-srv 是什么？

现代可扩展的 FTP 服务器\( TypeScript 版本\)

### 如何安装 @electerm/ftp-srv？

在项目目录运行 npm install @electerm/ftp-srv，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 electermftpsrv，可以用 npx electermftpsrv 直接运行。

### @electerm/ftp-srv 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @electerm/ftp-srv 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @electerm/ftp-srv 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=16。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @electerm/ftp-srv 可以商用吗？

@electerm/ftp-srv 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @electerm/ftp-srv 的下载量有多少？

根据 npm 官方下载统计，@electerm/ftp-srv 在 2026/10/14 – 2026/11/12 这 30 天内被下载 14,135 次，日均约 471 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @electerm/ftp-srv 还在维护吗？

最新版本 1\.1\.0 发布于 2026/10/29（15 天前）；至今共发布 11 个版本，其中过去 12 个月发布了 11 个；首次发布于 2026/04/25。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

