Legacy ZIP: две свои UTF8 записи, constructor/Open и проверенный ExtractAll

Учебные задачи

Полный own UUID workflow с настоящими файлами только при будущем ручном запуске: память→sourcefiles→два writer-пути→точный whitelist архива→extract в новый own-output→bytes/SHA256. Не getter-wrapper и не распаковка неизвестного архива.

Где и как запускать

Полный модуль управляемой формы только толстого Windows-клиента8.5.1.1343 собственной пустой файловой ИБ; будущие own UUID-temp файлы, actual0.

Подготовка

  1. Создайте отдельную новую пустую файловую ИБ на платформе ровно8.5.1.1343: корень конфигурации УчебныеАрхивыИБуферы85, управляемое приложение, русский встроенный язык, совместимость Не использовать. Рабочие базы/каталоги/архивы в этом уроке запрещены.

  2. В конфигурации0предметных объектов/регистров/общих реквизитов/подписок/заданий/внешних источников. Никаких расширений базы или сеанса. Полный guard server-процедуры проверяет это и отсутствие активной транзакции.

  3. Запускайте только толстый управляемый Windows-клиент8.5.1.1343, не сервер/тонкий/веб/мобильный. Желательно отдельный учебный Windows-профиль с ASCII именем OwnArchive85: legacyZIP API ограничивает национальные символы в исходных путях. SHA256 API доступен в толстом клиенте.

  4. Создайте отдельную внешнюю обработку OwnArchive85 с управляемой формой. Реквизиты ПодтверждаюСвойОпыт85 Булево исходноЛожь; ПутьОпыта85 Строка0 ТолькоПросмотр; Статус85 Строка0 многострочный ТолькоПросмотр. Назначьте ПриОткрытии; команда Выполнить85 и одноименный обработчик/кнопка, ИзменяетСохраняемыеДанныеЛожь.

  5. Вставьте весь основной модуль одного выбранного урока в форму. Уроки самостоятельны: не объединяйте одноименные процедуры. Буферный урок не создает файлов и не использует ПутьОпыта85.

  6. После подтверждения нового пустого стенда каждая файловая команда генерирует свой свежий UUID и проверяет отсутствие OwnArchive85-UUID в native клиентском временном каталоге. Путь не берется из пользовательской строки. Existing-файлы никогда не перезаписываются.

  7. Архивный fixture — только свои card.txt UTF8безBOM Книга;3;250;750 и manifest.txt со своим UUID/files=2. Файлы сначала реально записываются и сверяются с исходными bytes/размером/SHA256. Пути/маски/имена пользователя в writer не передаются.

  8. Архивы создаются в ПотокВПамяти двумя реальными API-путями: constructor(stream) и new-uninitialized→Открыть(stream). Для modern явно ТипФайлаАрхива.ZIP; пароль пустой, шифрование не заявляется. Записать финализирует writer; метода Закрыть у writer нет, он не выдумывается.

  9. Перед ИзвлечьВсе проверяются ровно2entry, exact whitelist плоских имен,0дубликатов/шифрования и точные размеры исходных байтов. Неизвестные/абсолютные пути/каталоги/traversal отказываются до распаковки. Это только свой только что построенный архив, не универсальный валидатор чужих ZIP.

  10. Output out-1/out-2 создаются внутри только своего UUID каталога после проверки отсутствия/пустоты. ИзвлечьВсе передается лишь обязательный Путь: native modern8.5 описывает второй параметр как Строка, поэтому неподтвержденное enum-значение туда не подставляется. Плоский whitelist не требует восстановленных каталогов.

  11. У ExtractAll результат подтверждается по обоим файлам: exact bytes, размеры иSHA256file=mempayload. Legacy Найти/Получить проверяются на реально полученной коллекции, с missing-name и index=count. Modern Find/Get native ошибочно описаны без аргументов и в этом уроке не угадываются.

  12. Ждать и callback уроки записывают отдельный свой новый payload.txt. Promise дает Неопределено; callback — только К; error callback3аргумента. Имя/bytes не принимаются от незнакомого объекта и completion не выдается за успешность без readback.

  13. Новые собственные учебные файлы/output-папки после ВАШЕГО ручного опыта остаются по показанному пути для контроля. Автоматического/рекурсивного удаления нет. При ошибке не повторяйте запись в старую UUID-папку; не подставляйте рабочий каталог.

  14. Здесь actual1C/files/archivewrites/network0: написаны только примеры и статические тесты. Архивные fixtures в offline-тестах исключительно in-memory и не являются резервными копиями. До ручного запуска итоговые сообщения — ожидаемый протокол.

&НаКлиенте
Процедура Выполнить85(Команда)
    Если Занято85 Тогда
        ВызватьИсключение "Свой предыдущий архивный опыт еще не завершен";
    КонецЕсли;
    Занято85 = Истина;
    Статус85 = "Собственный архивный опыт еще не подтвержден";
    Попытка
        К = ПодготовитьСвойКаталог85();
        ПутьОпыта85 = К.Корень;
        Карточка = ПолучитьДвоичныеДанныеИзСтроки("Книга;3;250;750", КодировкаТекста.UTF8, Ложь);
        Паспорт = ПолучитьДвоичныеДанныеИзСтроки("UUID=" + К.UUID + "|files=2", КодировкаТекста.UTF8, Ложь);
        Источники = Новый Соответствие;
        Источники.Вставить("card.txt", Карточка);
        Источники.Вставить("manifest.txt", Паспорт);
        Для Каждого Источник Из Источники Цикл
            ПроверитьНовыйСвойФайл85(К, Источник.Ключ);
            Источник.Значение.Записать(СвоеИмяФайла85(К, Источник.Ключ));
            ПроверитьСвойФайл85(К, Источник.Ключ, Источник.Значение);
            ПроверитьСвойХешФайла85(СвоеИмяФайла85(К, Источник.Ключ), Источник.Значение);
        КонецЦикла;
        ОтказПути = Ложь;
        Попытка
            СвоеИмяФайла85(К, "../foreign.txt");
        Исключение
            ОтказПути = Истина;
        КонецПопытки;
        Если Не ОтказПути Тогда
            ВызватьИсключение "Traversal должен быть отвергнут до любого файлового API";
        КонецЕсли;
        СобственныхФайлов = НайтиФайлы(К.Корень, "*", Ложь);
        Если СобственныхФайлов.Количество() <> 2 Тогда
            ВызватьИсключение "В новом UUID каталоге исходно ровно2своих файла";
        КонецЕсли;
        Для Режим = 1 По 2 Цикл
            Архив = СоздатьСвойАрхив85(К, Источники, Режим);
            ПроверитьИзвлечьСвойАрхив85(К, Источники, Архив, Режим);
        КонецЦикла;
        Статус85 = "UUID " + К.UUID + ": ctor и Open создали свои архивы; whitelist2файла; exact bytes/SHA256 после ExtractAll; traversal отказан. Собственная папка оставлена для ручного контроля";
    Исключение
        Статус85 = "Свой архив/состав/bytes/SHA256 не подтверждены. Собственная папка при наличии оставлена, ничего автоматически не удалено";
    КонецПопытки;
    Занято85 = Ложь;
КонецПроцедуры

&НаКлиенте
Функция СоздатьСвойАрхив85(К, Источники, Режим)
    Поток = Новый ПотокВПамяти;
    Запись = Неопределено;
    Попытка
        Если Режим = 1 Тогда
            Запись = Новый ЗаписьZipФайла(Поток);
        ИначеЕсли Режим = 2 Тогда
            Запись = Новый ЗаписьZipФайла;
            Запись.Открыть(Поток);
        Иначе
            ВызватьИсключение "Разрешены только свои constructor/Open варианты";
        КонецЕсли;
        Для Каждого Источник Из Источники Цикл
            ПроверитьСвойФайл85(К, Источник.Ключ, Источник.Значение);
            Запись.Добавить(СвоеИмяФайла85(К, Источник.Ключ), РежимСохраненияПутейZIP.НеСохранятьПути);
        КонецЦикла;
        Запись.Записать();
        Запись = Неопределено;
        Архив = Поток.ЗакрытьИПолучитьДвоичныеДанные();
    Исключение
        Запись = Неопределено;
        Попытка
            Поток.Закрыть();
        Исключение
            ВызватьИсключение "Свой writer/поток не завершены, output не подтвержден";
        КонецПопытки;
        ВызватьИсключение "Свой writer не создал подтвержденные данные";
    КонецПопытки;
    Если Архив.Размер() <= 0 Или Архив.Размер() > 65536 Тогда
        ВызватьИсключение "Собственный маленький архив должен быть непустым и не больше64КБ";
    КонецЕсли;
    Возврат Архив;
КонецФункции

&НаКлиенте
Процедура ПроверитьИзвлечьСвойАрхив85(К, Источники, Архив, Режим)
    Поток = Архив.ОткрытьПотокДляЧтения();
    Чтение = Неопределено;
    Попытка
        Чтение = Новый ЧтениеZipФайла(Поток);
        Элементы = Чтение.Элементы;
        Если Элементы.Количество() <> 2 Тогда
            ВызватьИсключение "Перед ExtractAll нужно ровно2известных собственных entry";
        КонецЕсли;
        Проверено = Новый Соответствие;
        Для Каждого Элемент Из Элементы Цикл
            Если Элемент.ПолноеИмя <> "card.txt" И Элемент.ПолноеИмя <> "manifest.txt" Тогда
                ВызватьИсключение "Запрещены неизвестные пути, каталоги, абсолютные имена и traversal до распаковки";
            КонецЕсли;
            Если Проверено.Получить(Элемент.ПолноеИмя) <> Неопределено Или Элемент.Зашифрован Тогда
                ВызватьИсключение "Дубликаты и шифрование не принадлежат своему fixture";
            КонецЕсли;
            Ожидание = Источники.Получить(Элемент.ПолноеИмя);
            Если Ожидание = Неопределено Или Элемент.РазмерНесжатого <> Ожидание.Размер() Тогда
                ВызватьИсключение "Не совпал точный исходный размер own-entry";
            КонецЕсли;
            Проверено.Вставить(Элемент.ПолноеИмя, Истина);
        КонецЦикла;

        Первый = Элементы.Получить(0);
        Если Элементы.Найти(Первый.ПолноеИмя) <> Первый Или Элементы.Найти("missing.txt") <> Неопределено Тогда
            ВызватьИсключение "Legacy Find должен находить actual индексный элемент и не находить missing";
        КонецЕсли;
        ОтказИндекса = Ложь;
        Попытка
            Элементы.Получить(Элементы.Количество());
        Исключение
            ОтказИндекса = Истина;
        КонецПопытки;
        Если Не ОтказИндекса Тогда
            ВызватьИсключение "Индекс count должен быть вне собственной коллекции";
        КонецЕсли;

        Каталог = К.Корень + "out-" + Строка(Режим) + "\";
        Папка = Новый Файл(Каталог);
        Если Папка.Существует() Тогда
            ВызватьИсключение "Свой каталог извлечения должен быть новым";
        КонецЕсли;
        СоздатьКаталог(Каталог);
        Если НайтиФайлы(Каталог, "*", Ложь).Количество() <> 0 Тогда
            ВызватьИсключение "Нельзя распаковывать даже свой архив поверх чужих файлов";
        КонецЕсли;
        Чтение.ИзвлечьВсе(Каталог);
        Чтение.Закрыть();
        Чтение = Неопределено;
        Поток.Закрыть();
        Поток = Неопределено;
        Если НайтиФайлы(Каталог, "*", Ложь).Количество() <> 2 Тогда
            ВызватьИсключение "После распаковки должны быть только два известных файла";
        КонецЕсли;
        Для Каждого Источник Из Источники Цикл
            Путь = Каталог + Источник.Ключ;
            Файл = Новый Файл(Путь);
            Если Не Файл.Существует() Или Не Файл.ЭтоФайл() Или Файл.Размер() <> Источник.Значение.Размер() Тогда
                ВызватьИсключение "Извлеченный собственный файл отсутствует или имеет другой размер";
            КонецЕсли;
            Если Base64Строка(Новый ДвоичныеДанные(Путь)) <> Base64Строка(Источник.Значение) Тогда
                ВызватьИсключение "Извлеченные собственные bytes не совпали";
            КонецЕсли;
            ПроверитьСвойХешФайла85(Путь, Источник.Значение);
        КонецЦикла;
    Исключение
        Если Чтение <> Неопределено Тогда
            Попытка
                Чтение.Закрыть();
            Исключение
                Попытка
                    Поток.Закрыть();
                Исключение
                    ВызватьИсключение "Reader и его поток не закрыты, результат неподтвержден";
                КонецПопытки;
                ВызватьИсключение "Reader не закрыт, результат неподтвержден";
            КонецПопытки;
        КонецЕсли;
        Если Поток <> Неопределено Тогда
            Попытка
                Поток.Закрыть();
            Исключение
                ВызватьИсключение "Свой поток не закрыт, результат неподтвержден";
            КонецПопытки;
        КонецЕсли;
        ВызватьИсключение "Свой архивный roundtrip не подтвержден";
    КонецПопытки;
КонецПроцедуры

&НаКлиенте
Функция ПодготовитьСвойКаталог85()
    ПроверитьСвоегоКлиента85();
    UUIDОпыта = Строка(Новый УникальныйИдентификатор);
    Если UUIDОпыта = "00000000-0000-0000-0000-000000000000" Тогда
        ВызватьИсключение "Нельзя использовать пустой UUID";
    КонецЕсли;
    База = КаталогВременныхФайлов();
    Если Прав(База, 1) <> "\" Тогда
        База = База + "\";
    КонецЕсли;
    Корень = База + "OwnArchive85-" + UUIDОпыта + "\";
    Папка = Новый Файл(Корень);
    Если Папка.Существует() Тогда
        ВызватьИсключение "Свой UUID-каталог должен быть новым; перезапись запрещена";
    КонецЕсли;
    СоздатьКаталог(Корень);
    Если Не Папка.Существует() Или Не Папка.ЭтоКаталог() Тогда
        ВызватьИсключение "Собственный новый каталог не создан";
    КонецЕсли;
    Возврат Новый Структура("UUID,Корень", UUIDОпыта, Корень);
КонецФункции

&НаКлиенте
Функция СвоеИмяФайла85(К, Имя)
    Если СтрРазделить("card.txt,manifest.txt,payload.txt", ",").Найти(Имя) = Неопределено Тогда
        ВызватьИсключение "Разрешены только свои три фиксированных имени без путей/масок";
    КонецЕсли;
    Возврат К.Корень + Имя;
КонецФункции

&НаКлиенте
Процедура ПроверитьНовыйСвойФайл85(К, Имя)
    Файл = Новый Файл(СвоеИмяФайла85(К, Имя));
    Если Файл.Существует() Тогда
        ВызватьИсключение "В собственном UUID опыте перезаписывать файл нельзя";
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьСвойФайл85(К, Имя, Данные)
    Путь = СвоеИмяФайла85(К, Имя);
    Файл = Новый Файл(Путь);
    Если Не Файл.Существует() Или Не Файл.ЭтоФайл() Или Файл.Размер() <> Данные.Размер() Тогда
        ВызватьИсключение "Нужен свой реальный файл с точным размером";
    КонецЕсли;
    Факт = Новый ДвоичныеДанные(Путь);
    Если Base64Строка(Факт) <> Base64Строка(Данные) Тогда
        ВызватьИсключение "Свой файл не совпадает побайтно с исходным payload";
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьСвойХешФайла85(Путь, Данные)
    ИзПамяти = Новый ХешированиеДанных(ХешФункция.SHA256);
    ИзПамяти.Добавить(Данные);
    ИзФайла = Новый ХешированиеДанных(ХешФункция.SHA256);
    ИзФайла.ДобавитьФайл(Путь);
    Если ТипЗнч(ИзФайла.ХешСумма) <> Тип("ДвоичныеДанные")
        Или Base64Строка(ИзФайла.ХешСумма) <> Base64Строка(ИзПамяти.ХешСумма) Тогда
        ВызватьИсключение "SHA256 собственного файла не совпал с исходными байтами";
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Перем Занято85;

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    Занято85 = Ложь;
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьСвоегоКлиента85()
    ПроверитьСеанс85();
    #Если ТолстыйКлиентУправляемоеПриложение Тогда
        Система = Новый СистемнаяИнформация;
        Если (Система.ТипПлатформы <> ТипПлатформы.Windows_x86 И Система.ТипПлатформы <> ТипПлатформы.Windows_x86_64) Или Не ПодтверждаюСвойОпыт85 Тогда
            ВызватьИсключение "Только свой новый пустой Windows-стенд и толстый управляемый клиент8.5.1.1343";
        КонецЕсли;
    #Иначе
        ВызватьИсключение "Этот own файловый урок не предназначен для сервера, тонкого, веб или мобильного клиента";
    #КонецЕсли
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ПроверитьСеанс85()
    ПроверитьСвойПустойСтенд();
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ПроверитьСвойПустойСтенд()
    Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
        ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
    КонецЕсли;
    Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
        ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
    КонецЕсли;
    Источники = Новый Массив;
    Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
    Для Каждого Источник Из Источники Цикл
        Расширения = РасширенияКонфигурации.Получить(, Источник);
        Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
            ВызватьИсключение "Native список расширений должен иметь тип Массив";
        КонецЕсли;
        Если Расширения.Количество() <> 0 Тогда
            ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
        КонецЕсли;
    КонецЦикла;
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
    КонецЕсли;
КонецПроцедуры

Разбор по шагам

  1. Запустите команду только в своем пустом стенде. Создается новый UUID-temp-root, существующий каталог/файл никогда не заменяется.

  2. Формируются две конкретные UTF8безBOM записи card/manifest, записываются как новые свои файлы и подтверждаются побайтно/SHA256file=mem.

  3. Проверка ../foreign.txt должна отказать внутри собственного whitelist-helper до вызова файлового API. Исходный каталог содержит ровно2своих файла.

  4. В первом проходе реально создаем writer на собственном ПотокВПамяти через stream-конструктор. Во втором — неинициализированный writer и Открыть того же вида потока; файлы добавляются явно без сохранения путей.

  5. Записать финализирует каждый собственный writer, после чего закрытие потока дает actual archive bytes. Для маленького fixture ограничение64КБ, не обещание сжатия короче исходника.

  6. Новый reader читает свои только что созданные bytes. Проверяем2entry, только card/manifest, точные размеры/0дубликатов/0шифрования; не допускаются пути или каталоги.

  7. Legacy Get(0) возвращает реальный первый элемент; Find его actual полного имени возвращает тот же элемент. Find(missing.txt)=Неопределено; Get(count) обязан отказать.

  8. Создайте пустой новый out-1/out-2 только под own UUID, затем ИзвлечьВсе(Каталог). Reader закрывается до его read-stream; неизвестный второй параметр modern не используется.

  9. В output подтверждаем лишь2точных файла, actual bytes/размеры и SHA256 относительно original own payload. Root/input/files не меняются после сравнения.

  10. Итог после обоих constructor/Open roundtrip и отрицательных тестов. Свой каталог остается для пользователя; никаких backup-файлов и automatic cleanup.

Как проверить результат

  1. Две исходные записи и два extract-output дают exact UTF8 bytes/SHA256, не только непустые файлы.

  2. Traversal/неизвестные entries/дубликаты/шифрование/неправильные размеры запрещены до ExtractAll.

  3. Constructor(stream) и Open(stream) реально используются отдельно; reader/stream корректно завершены.

  4. Legacy предупреждение/modern неоднозначные члены показаны честно; actual1C/files/archivewrites0.

Ожидаемый результат

Ожидаемый ручной итог:2sourcefiles, два собственных memoryZIP и2пустых→заполненных output, exact bytes/SHA256. Собственная UUID-папка остается; это не уже полученный runtime-результат.

На что обратить внимание

  1. Legacy ЗаписьZipФайла/ЧтениеZipФайла не рекомендуются с8.3.26: для нового кода выбирайте modern пример.

  2. CRC/SHA256 и точное совпадение с собственными ожидаемыми bytes проверяют целостность этого fixture, не доверие к произвольному чужому архиву.

  3. ИзвлечьВсе может писать все entries; whitelist/count/размеры обязательны до его вызова. Не копируйте этот путь на неизвестные ZIP без дополнительных лимитов/проверки внешних атрибутов.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.