废弃工业机械上生锈的齿轮与飞轮

它是怎么来的

2014 年,Jake A. Smith 在维护 AOL 的内容管理系统,要把 PHP 从 5.2 升到 5.3,其中一个动作是丢掉 pecl_http 扩展的 v1 版本,而里面有个叫 http_build_url 的函数被系统里几十处代码调用。他不想动那些调用点,就自己写了一个同名的兼容函数,只在系统没提供时生效,顺手发到 Packagist 上,方便别人的迁移也省点事。他当时的判断是:撑一两年,等社区给出更好的方案就没人用了。

结果是十二年

到今年 9 月,这个包在 Packagist 上累计被安装约 1986 万次,最近 30 天还有 40 万次以上。它还被多语言插件 WPML 直接内置进代码库(官方称覆盖 150 万以上站点),域名处理库 idna-convert 依赖它,于是它又跟着进入了法国内容管理系统 SPIP 的源码,并被打包进 Debian 与 Ubuntu。

一个躺了多年的 bug

作者几年后回头看这个包,才发现有一个存在很久的问题:当 URL 路径以斜杠结尾时,函数会把路径里所有的字母「a」都删掉。原因很朴素——当年为了处理尾斜杠,他在路径末尾补了一个「a」再查找替换掉;而当路径本身就以斜杠结尾时,被替换掉的正好是补上的那个「a」,以及路径里所有的「a」。

他的退役方式值得学

摆在面前有三条路:自己回到已经离开近十年的 PHP 世界继续维护、把包交给愿意接手的人、或者干脆弃用。他选了第三条,并给出两个替代品:社区的 PHP League URI 库,以及 PHP 8.5 起语言内置的标准 URI 接口。他也解释了为什么不交给新维护者:一个被广泛安装、但下游没人验证过的新维护者,正是攻击者最想要的位置。之后连那一行 bug 他都不打算修——十二年没人改过的代码,一根手指的改动都可能伤到某个使用者,而他身边已经没有能支持这件事的人。

补一句结尾:AOL 那套系统,到 2020 年整个平台关停,都没换掉这个「临时」补丁。

给自己留一份清单

每个团队都有几处这样的东西:一行临时改动、一个绕过写法、一份「先这样以后再改」的脚本。它们的寿命从来不取决于当初的承诺,而取决于「还能跑」这件事有多大的说服力。与其每年许愿清理,不如把它们列出来,写清替代方案与迁移步骤,然后按节奏一个个送走。临时方案本身没有错,忘记它是临时的,才是代价。