Временный костыль

В 2014 году компания AOL переводила свою систему управления контентом с PHP 5.2 на 5.3. Часть этого обновления заключалась в отказе от расширения pecl_http версии 1, которое предоставляло функцию http_build_url(). CMS работала с множеством URL, и эта функция вызывалась в десятках мест по всему коду. Переписывать эти вызовы было нежелательно. Функция казалась достаточно простой для самостоятельной реализации, поэтому разработчик написал собственный http_build_url(), определяемый только в случае, если встроенная версия ещё не загружена. Старый код так и не узнал об изменении.

В то время Composer только набирал популярность, что упростило распространение решения. Казалось логичным опубликовать пакет в Packagist — предполагалось, что он пригодится год-два, пока PHP-сообщество не двинется дальше.

Столько установок

Но «временное» решение оказалось вечным. Пакет установлен из Packagist почти 20 миллионов раз и ежемесячно получает свыше 400 тысяч новых установок.

Статистика установок http_build_url на Packagist на 15 сентября 2026 года: 19 864 271 всего установок и 401 308 за последние 30 дней. Ежедневные установки постепенно растут от нуля в 2014 году до примерно 13 000 в день в 2026 году.

Оказалось, что Composer — лишь часть истории. WPML, ведущий в отрасли многоязычный плагин для WordPress, встраивает полифилл прямо в свой исходный код, и компания заявляет об установке на более чем 1,5 миллиона сайтов. От пакета также зависит библиотека для работы с доменными именами idna-convert, благодаря чему код попал в исходники SPIP (французская система управления контентом), а затем был упакован в Debian и Ubuntu. Практически гарантированно, что вы посетили веб-сайт, на котором всё ещё работает этот код.

Никогда не представлял, что это зайдёт так далеко.

Возвращение к коду

Масштаб распространения стал ясен только несколько месяцев назад, когда разработчик впервые за несколько лет посмотрел на пакет. Количество пользователей было известно давно. К 2021 году он уже несколько лет не работал на PHP, и объём загрузок удивил настолько, что он попросил нового мейнтейнера. Откликнулись трое. Вскоре после этого запроса семья столкнулась с неожиданной потерей, которая на время перевернула их жизнь. Он не сумел вернуться к вопросу с пакетом, и это его ошибка. После того как дела нормализовались, внимание переключилось на другие приоритеты, и пакет был забыт на годы.

Вместе с впечатляющими цифрами появилась пригоршня GitHub-issues. В одной из них описана проблема: при добавлении пути к URL с завершающей косой чертой функция удаляет все буквы «a» из этого пути. Так много для «простой функции». Под комментарием // Workaround for trailing slashes код добавляет «a» к пути, чтобы всегда был последний сегмент для удаления, а затем вырезает его с помощью find-and-replace. Когда путь заканчивается косой чертой, последний сегмент — это просто «a», и операция replace удаляет все остальные «a» в пути вместе с ним. Удивительно, что баг оставался незамеченным столько времени.

Перед разработчиком встала задача: вернуться в PHP после почти десяти лет перерыва, передать пакет одному из предложивших себя людей или оставить всё как есть.

Ни один из вариантов

Пакет всегда был задуман как временный, поэтому он будет снят с поддержки. URI-библиотека PHP League уже долгие годы является ответом сообщества, а PHP 8.5 теперь поставляется со встроенным стандартизированным URI API (спасибо jawira за указание на это). Обе альтернативы лучше, чем 174-строчный полифилл 2014 года. Поддержка пакета только замедлит миграцию, которую все должны выполнить, а передача новому мейнтейнеру добавит риск. Разработчик ничуть не сомневается в компетентности предложивших помощь людей, и ozh продолжает развивать форк для YOURLS. Но широко установленный пакет с новым мейнтейнером, которого никто downstream не проверил, — это именно то, что ищут злоумышленники. Видеоролик Veritasium об уязвимости в xz Utils — лучшее объяснение того, как это может происходить.

Пакет останется доступен для установки, но не будет получать новые исправления, включая исправление бага с пропадающей буквой «a». После столь долгого периода без обновлений даже однострочное исправление может иметь непредвиденные последствия для кого-то, и некому будет это поддерживать. README показывает, как переключиться на альтернативу.

Код был написан для облегчения болезненной миграции — как для себя, так и для всех остальных, кто через неё проходил. Спасибо всем, кто отправлял pull-request или предлагал помощь в поддержке, и спасибо тем, кто продолжал открывать issues долгие годы после того, как я перестал их читать. Для временного решения это была хорошая жизнь.

P.S. Платформа AOL никогда не перешла на внутреннего мейнтейнера этого «временного» полифилла. Он работал там до полного отключения платформы примерно в 2020 году.