# Март 2018 — безопасная загрузка файлов: draft-review Статус: принято в публикационный слой 31 июля 2026 после независимого audit. Эта партия существует как три ревизии из `web/scripts/upgrade-2018-03.mjs --print-revisions`; слой `web/data/editorial-revisions.mjs` сопоставляет их только по стабильным slug, не меняя даты, автора или историю Git. | Slug | Главный вопрос | Основной текст без источников | | --- | --- | ---: | | `editorial-2018-03-practice-safe-uploads` | Как принять JPEG или PNG для аватара без доверия к имени и MIME-типу формы? | 5 666 знаков | | `editorial-2018-03-mechanism-safe-uploads` | Какие признаки файла можно использовать для какой проверки? | 6 209 знаков | | `editorial-2018-03-field-safe-uploads` | Как выдать владельцу приватный PDF, если файл хранится вне веб-корня? | 5 688 знаков | ## 1. Факты и техника — пройдено ### Практика: приём аватара - Проверены [коды ошибок загрузки PHP](https://www.php.net/manual/en/features.file-upload.errors.php), [move_uploaded_file](https://www.php.net/manual/en/function.move-uploaded-file.php), [finfo_file](https://www.php.net/manual/en/function.finfo-file.php), [ограничение getimagesize как валидатора](https://www.php.net/manual/en/function.getimagesize.php) и [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html). - Пример проверяет `UPLOAD_ERR_OK`, прикладной лимит, MIME-тип через Fileinfo и размеры изображения до переноса. `getimagesize()` используется только для размеров, не как доказательство корректности изображения. - Ограничения названы прямо: нет антивирусной проверки, CSRF-защиты и обработки миниатюр. ### Механизм: границы доверия - Проверены [RFC 7578 для multipart/form-data](https://www.rfc-editor.org/rfc/rfc7578), [коды ошибок PHP](https://www.php.net/manual/en/features.file-upload.errors.php), [Fileinfo](https://www.php.net/manual/en/function.finfo-file.php), [getimagesize](https://www.php.net/manual/en/function.getimagesize.php) и [OWASP](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html). - Воспроизводимый `curl`-пример не утверждает конкретный результат базы magic: он показывает разницу между заявленным клиентом `type` и типом, который определяет Fileinfo. - Статья не называет Fileinfo антивирусом и не переносит ответственность за лимит всего запроса на одну PHP-функцию. ### Поле: выдача приватного PDF - Проверены [OWASP для размещения файлов вне webroot](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html), [header()](https://www.php.net/manual/en/function.header.php), [readfile()](https://www.php.net/manual/en/function.readfile.php) и [RFC 6266 для Content-Disposition](https://www.rfc-editor.org/rfc/rfc6266). - В коде ID документа и текущий пользователь участвуют в одном SQL-запросе; путь строится только из ключа, прошедшего контрактное регулярное выражение. - Ограничения не скрыты: в примере нет Range, кеширования, ограничения частоты и эффективной выдачи больших файлов. Версионная оговорка: код ориентирован на PHP 7.2. В статьях не используются приёмы, добавленные позднее; актуальные страницы PHP Manual взяты как первичный справочник функций и их ограничений. ## 2. Редактура и голос — пройдено после исправления стоп-условия - Каждая статья отвечает на один вопрос и начинает с наблюдаемой ситуации, а не с общего рассуждения о безопасности. - Для `mechanism` и `field` первоначальный строгий audit обнаружил недостаточно явную постановку проблемы. В первые два предложения добавлены формулировки `Симптом:` и `Цена ошибки`; сильная исходная подводка сохранена дальше в том же абзаце. - Повторный CLI-audit подтвердил: обе статьи содержат симптом и цену ошибки в первых 420 знаках; длины — 6 209 и 5 688 знаков соответственно. - Речь соответствует 2018 году: короткие технические абзацы, «давайте» и «я бы» только там, где автор делает практический вывод; нет обещаний универсального решения, поздних инструментов и шаблонных оборотов. - Во всех трёх ревизиях есть проблема, таблица, воспроизводимый пример, порядок действий, ограничения, один рисунок и минимум четыре первичных или нормативных источника. ## 3. Визуал и выпуск — пройдено для черновика - `xmllint --noout` прошёл для трёх SVG: `php-upload-avatar-contract.svg`, `php-upload-trust-signals.svg`, `php-private-download-flow.svg`. - В локальном рендере SVG проверены title, доступное описание, границы текста и масштаб 1280×720: 31, 26 и 20 текстовых узлов соответственно; выходов за границы нет. - На первой схеме во время визуальной проверки найден и исправлен контраст номеров этапов: цвет изменён с белого на тёмный `rgb(46, 82, 103)`. - Каждый рисунок будет иметь осмысленный `alt` и подпись через данные ревизии. Таблицы обёрнуты в `table-scroll`; текущие стили блога добавляют горизонтальную прокрутку при минимальной ширине таблицы 620px. - Production-сборка и проверка опубликованных URL не запускались намеренно: статьи ещё не интегрированы в `web/data/articles.json`. Это выпускной шаг основного агента, а не основание менять архив из этой ветки. ## Повторённые команды ```sh node --check web/scripts/upgrade-2018-03.mjs node web/scripts/upgrade-2018-03.mjs --print-revisions xmllint --noout \ web/public/assets/editorial/2018/php-upload-avatar-contract.svg \ web/public/assets/editorial/2018/php-upload-trust-signals.svg \ web/public/assets/editorial/2018/php-private-download-flow.svg ``` Результат: три ревизии готовы для точечной интеграции без перезаписи остальных статей. ## Приёмка основного агента - Повторно пройден строгий `audit-quality-batch.mjs`: 5 666 / 6 209 / 5 688 знаков основного текста; в каждой статье найдены рисунок с `alt`, таблица, код, порядок действий и отдельный раздел источников. - Проверены безопасный import модуля и CLI-вывод ровно трёх ревизий. Production build после интеграции прошёл и сгенерировал 374 статические страницы.