摘要

  • STOU 让服务器在当前目录中为新文件选择不冲突的路径,但客户端若按本地规则改写返回值,仍可能失去对那份远端文件的引用。
  • RFC 1123 把实际路径固定在传输前的 125 FILE: 或 150 FILE: 响应中,并要求 User-FTP 接受任意远端路径字符串;机器可解析的边界与字符串保真同样重要。
  • 路径分配、传输完成、多个路径是否指向同一底层存储文件、访问权和长期保管,各自需要证据。一个看似“清理过”的本地名字不能替代服务器原样返回的标识。

客户端把正确答案改错了

假设服务器在一条初步响应中返回 150 FILE: Mix Case/Job 07。一个客户端把它放进界面时转义了空格,另一个为了适应本机习惯把 / 换成反斜杠,第三个在存入数据库前统一转成小写。屏幕上都还能看出“差不多是那个名字”,可是下次把改写后的字符串送回服务器时,它们未必仍指向服务器当初分配的路径。

问题不在于某一种大小写、分隔符或显示形式天然错误。问题在于决定这些规则的是远端系统,而不是接收回执的本地客户端。服务器已经完成了命名;客户端所谓的“正规化”如果改变了标识,就不是清理,而是未经授权地重新命名。

这种失败比一次明显的解析错误更隐蔽。程序可能成功提取了 FILE: 后的文字,成功把它显示给用户,也成功保存了一份字段,只是在这些步骤之间把显示值、存储值和再次引用时的值混成了一个。直到用户尝试领取、移动或删除文件,跨平台差异才变成“文件不存在”。

FTP 从来没有规定一套全球路径语法

RFC 172 在 1971 年把文件描述为由系统内路径名唯一识别的数据,却没有统一不同主机的命名习惯。早期 FTP 用户必须按照远端文件系统的约定表达路径。网络可以标准化操作,却不能假装所有操作系统用同一种字符、层级和书写方法命名文件。

这意味着一次文件传输跨过两种接口。数据通过网络抵达另一台机器,路径字符串则进入另一套命名制度。普通存储操作由客户端提供路径;若该路径已经存在,原有内容可能被替换,若不存在则可能创建新文件。发送者既要说对远端语言,也要承担自己选择的位置与既有名字碰撞的后果。

RFC 172 还把选择性访问留给常驻文件系统。FTP 交换标识、口令和路径,并没有接管服务器的保护模型。这条边界后来仍然重要:即便服务器负责生成名字,也不代表客户端因此获得写入资格;即便客户端拿到路径,也不代表它取得读取或删除权限。

从异构路径的角度看,STOU 做了两件互相依赖的事。第一件是让掌握远端命名规则的一方选择新名字;第二件是把选择结果返回给不掌握这些规则的客户端。若第二步允许客户端按本地约定重写,第一步的优势就被悄悄抵消了。

FILE: 是解析边界,不是显示提示

RFC 1123 在 1989 年规定,服务器收到 STOU 后,必须在数据传输之前以 125 FILE: pppp 或 150 FILE: pppp 的精确形式返回实际文件名。固定的 FILE: 标记让程序知道路径从哪里开始,预备响应代码则说明这个名字属于即将发生的写入,而不是已经完成的写入。

同一文档还要求 User-FTP 把远端路径当作任意字符字符串接受,而不是套用本地操作系统的命名规则。这不是在说任意字符串都能绕过服务器验证;服务器仍决定什么路径有效。它要求的是客户端不要因为自己运行在另一种系统上,就拒绝、截短或改写一个服务器已经确认的远端路径。

因此,界面显示与协议标识需要分层。界面可以为了可读性显示转义、方向提示或字体替代,但持久化字段必须保留用于再次引用的原始远端字符串。若用户从界面复制的是显示形式,软件也需要明确它是否能无损还原。把人眼看到的文本直接当作存储标识,会让展示层的便利侵入协议层。

125 FILE: 或 150 FILE: 也只证明服务器选了“将被写入”的路径。后续 226 或 250 才能提供传输完成证据;425、426、451、551、552 等失败响应仍可能在名字分配之后出现。客户端忠实保存路径,不等于可以提前宣称每个字节已经到达,更不等于证明未来保留、备份或不可变。

没有路径参数,把命名权交给远端

RFC 959 在 1985 年把 STOU 列为新增的可选 FTP 命令,其形式语法是 STOU <CRLF>。与 STOR pathname 相比,客户端没有提供新文件的路径。服务器应在当前目录中创建一个名字对该目录唯一的文件。

“当前目录”划定了授权和唯一性的范围。会话此前可能已经选择了某个目录,但服务器仍控制怎样在其中分配不冲突的路径。规范没有承诺这个名字全球唯一、永久保留,也没有把它定义为内容指纹。一个客户端若把远端路径转换成自己熟悉的全局 URI、摘要或本地文件名,并以此替换原值,就加入了规范并未提供的“永久代表某一底层文件记录”语义。

RFC 959 本身在返回名字的阶段出现了矛盾。正文要求把生成名放进所谓“250 Transfer Started”响应,可同一文档把 125 和 150 用于传输开始前后,把 250 定义为文件动作完成;STOU 的响应表也列出先有 125 或 150,再有 226 或 250。RFC 1123 的修正不仅换了一个数字,也把远端路径从含混的服务器文本里划出稳定的机器接口。

如果客户端仍然从任意描述文字猜名字,或从最终成功消息中抓取看起来像路径的片段,它就绕过了这项修正。解析器也许能在某台服务器上工作,却把该服务器的文案变成隐含依赖。迁移服务器或更换本地平台时,这种依赖会以“路径消失”的形式暴露。

为什么另设命令,而不是让 STOR 猜测

1985 年 7 月的 RFC 949 讨论了打印机、传真机和卡片穿孔机的池目录。用户想把作业放进队列,并不一定关心服务器内部使用哪个文件名。文档比较了修改 STOR、增加参数以及把无参数存储当作特殊情况等方案,最终选择新的 STOU 命令。重新解释既有 STOR 被认为不理想,而命令名分派更容易扩展。

独立命令把两种路径来源固定在语法上。见到 STOR,接收端知道路径来自客户端;见到 STOU,服务器知道自己要分配名字。若把两者合成一个依赖本地默认值的接口,客户端库可能把“省略路径”误当作“使用本地文件名”,重新把远端命名权偷偷拿回来。

RFC 949 还明确要求把生成名告知发送者,而不是让披露成为可选项。客户端必须知道远端答案,才能在后续引用它。但同一提案强调,STOU 不用于绕过主机的访问控制、认证或记账。路径分配是一项可观察的服务器决定,不是一张访问令牌。

池中的名字原本就可能不同

更早的 RFC 505 在 1973 年提出,把不知道收件人口令的来件先放入池目录,通知收件人后再由其复制。提议的 POOL id name 接受一个期望名字,却允许服务器添加前缀或后缀来保持池内唯一。因此,发送者写出的名字与服务器最终使用的本地路径可能不同。

这个方案把收件人身份、池位置、名字调整和通知分开,也避免陌生发送者直接消耗收件人目录配额或覆盖现有文件。它同时承认,高层协议当时需要理解太多不同操作系统的细节。服务器调整名字,正是对异构命名现实的一种回应。

不过 RFC 505 是提案,不是部署普查。后来 STOU 在文献关系上半取代它,不能证明 POOL 曾成为普遍服务。这里可以继承的是接口问题:发送者的标签是愿望,服务器返回的路径才是远端命名空间中的决定。客户端若又把这个决定改回愿望的样子,便让两者重新混淆。

新路径没有自动续上旧文件

RFC 3659 讨论流模式重启时说,REST 之后的传输通常应使用 RETR 或 STOR。服务器可以允许 APPE 或 STOU,但不推荐;REST 后的 STOU 被称为语义未定义,因为把剩余内容存进一个新分配的名字,通常不能继续写入原来的部分文件。

这里再次出现抽象边界。重启位置依赖同一个已经部分写入的文件,STOU 却产生一个新路径。客户端不能因为本地任务名相同,或因为自己把两个服务器路径正规化成同一显示值,就推断它们延续同一底层存储文件的写入。字符串相似、业务意图相同与远端文件的延续关系不是一回事。

RFC 3659 定义的 Unique= 列表事实也不能替代 STOU 路径。相同 token 表示两个名字指向同一底层存储文件;token 不透明、区分大小写,其映射只须至少在控制连接存续期间保持一致。规范没有把它定义为内容哈希,也不能用它比较两个不同文件的字节内容。STOU 解决的是当前目录中新路径不碰撞,Unique= 解决的是不同路径是否指向同一底层文件记录。同一底层存储文件可以有多个路径,一个唯一分配的路径也不必成为永恒不变的文件编号。

文档还警告,不应把可能绕过保护机制的文件系统标识作为公开 Unique= token 的基础。客户端忠实保存服务器返回值,不意味着服务器应该暴露内部权限捷径。字符串保真与最小披露可以同时成立:返回足以再次引用的路径,却不把内部权力包装成可以长期代表文件的字段。

登记名称不能替代实际字符串

RFC 5797 建立 FTP 命令和扩展登记,STOU 作为可选基础命令引用 RFC 959 和 RFC 1123。登记的主要目的是避免命令与扩展名称互相冲突,而不是证明某项功能得到认可、广泛实现或正在使用。

当前的 IANA FTP Commands and Extensions 登记 是协议分配的现时证据,不是端点能力或路径格式的样本。基础命令的 base FEAT 代码是不可变占位符,也不应被解释成服务器会在 FEAT 中返回一行 base。客户端若根据登记表预设某种名字格式,仍是在用全局元数据覆盖远端实际回答。

保真不是安全证明

RFC 2577 记录了标准 FTP 的口令、控制信息和数据不加密,以及数据连接窃取和替换等独立风险。保存一个完全正确的 FILE: 路径,不会认证内容、证明数据对端或授权收件人。STOU 也不阻止之后重命名、删除或改变可见性。

但安全边界并不削弱字符串边界。恰恰相反,一个协议若把命名权交给服务器,客户端就更需要区分三件事:服务器返回了什么,界面怎样展示它,本地系统怎样保存它。只有第一项能够再次代表远端决定。跨系统软件的成熟,不是把每个陌生名字修成家乡格式,而是知道哪些差异属于对方的权力,必须原样穿过自己的抽象层。