Как исправить ошибки «Quote command returned error» и «Access denied» при работе с FTPСоединение в 1С

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

Приветствуем вас, коллеги! Работа с протоколом FTP из встроенного языка 1С:Предприятия — стандартная задача при реализации интеграционных механизмов, обмене данными или выгрузке каталогов товаров на сайт. Однако на практике разработчики часто сталкиваются со специфическими и неочевидными ошибками платформы 1С при вызове методов объекта FTPСоединение. Две самые распространенные проблемы — это отказ в создании каталога с системной ошибкой «Quote command returned error» и сбой проверки существования файлов с ошибкой «Access denied to remote resource».

В этой статье мы подробно разберем, почему возникают эти сбои, как особенности работы FTP-демонов на стороне сервера влияют на поведение 1С, и напишем надежный, отказоустойчивый код для выгрузки файлов и создания структуры каталогов любой вложенности.

Почему возникает ошибка «Quote command returned error» при создании каталога?

При вызове метода СоздатьКаталог() объект FTPСоединение отправляет на FTP-сервер низкоуровневую команду протокола MKD (Make Directory). Ошибка «Quote command returned error» означает, что FTP-сервер вернул код ошибки (чаще всего это код 550) в ответ на этот запрос, а платформа 1С транслировала этот ответ в системное исключение.

Давайте проанализируем основные причины такого поведения сервера:

  1. Целевой каталог уже существует на сервере. Большинство FTP-серверов (например, под управлением Linux, таких как *vsftpd* или *ProFTPD*) считают повторную команду создания уже существующей папки некорректной и сразу же возвращают ошибку.
  2. Попытка создания «глубокого» пути за один вызов. Стандартный метод СоздатьКаталог() в 1С не умеет рекурсивно создавать вложенные подкаталоги. Например, если на сервере есть папка upload, и мы пытаемся выполнить команду Соединение.СоздатьКаталог("upload/import_files/images"), сервер вернет ошибку, так как папки import_files еще не существует. Путь должен создаваться строго пошагово.
  3. Недостаточно прав у пользователя. Хотя системные администраторы могут уверять, что права предоставлены в полном объеме, права на запись файлов и права на создание новых директорий — это разные привилегии в конфигурации FTP-сервера.

Разбор проблемы «Access denied to remote resource» при вызове НайтиФайлы()

Эта ошибка часто сбивает разработчиков с толку. Платформа 1С сообщает об отказе в доступе (аутентификации) к удаленному ресурсу при попытке проверить наличие файла или каталога методом НайтиФайлы(). На самом деле реальных причин здесь три:

Во-первых, некорректное использование слэшей на конце путей. Символы прямого слэша / в конце строки пути часто воспринимаются сервером некорректно. Например, путь upload/import_files/ может быть распознан сервером как попытка обратиться к пустому элементу каталога внутри папки import_files. В результате сервер возвращает ошибку синтаксиса или ошибку отсутствия ресурса, которую 1С интерпретирует как проблему с доступом.

Во-вторых, рекурсивный поиск файлов. По умолчанию, если вы вызываете поиск файлов в подкаталогах, 1С посылает серверу команду получения списка с ключом рекурсии (например, LIST -R). В целях безопасности и снижения нагрузки многие публичные и веб-серверы блокируют рекурсивные команды. Сервер отклоняет запрос, а 1С выдает ошибку доступа.

В-третьих, чувствительность к регистру (Case Sensitivity). Если ваш FTP-сервер работает под управлением ОС Linux/Unix (что является стандартом для веб-хостингов), пути upload/import_files и Upload/Import_Files — это абсолютно разные каталоги. Несовпадение хотя бы одного символа в регистре приведет к ошибке доступа или отсутствия ресурса.

Анализируем влияние смены каталога внутри цикла

Рассмотрим распространенную архитектурную ошибку, которую часто допускают при написании выгрузки. Посмотрим на этот паттерн поведения кода:


// ПЛОХОЙ ПРИМЕР: Смена каталога в цикле выгрузки файлов
Для Каждого НайденныйФайл Из МассивФайлов Цикл
    Если НайденныйФайл.ЭтоКаталог() Тогда
        // Постоянная смена текущего каталога
        Соединение.УстановитьТекущийКаталог("upload/" + НайденныйФайл.Имя); 
        // Какая-то работа с файлами...
    КонецЕсли;
КонецЦикла;

При таком подходе состояние сессии FTP-соединения постоянно меняется. Метод УстановитьТекущийКаталог() изменяет рабочую область. Если внутри цикла происходит сбой или вложенность каталогов увеличивается, относительные пути ломаются. Программа теряет контекст, где именно она сейчас находится на сервере, и последующие вызовы создания папок или записи файлов начинают аварийно завершаться.

Правильное решение: Установить текущий каталог (корневую папку обмена) один раз при инициализации соединения, а всю дальнейшую работу по созданию каталогов и записи файлов вести с использованием полных (абсолютных) путей относительно корня FTP-сервера, избегая постоянного переключения контекста.

Пошаговое руководство по исправлению ошибок работы с FTP

Разберем практические шаги по оптимизации кода для стабильного обмена данными с FTP-сервером.

Шаг 1. Очистка путей от лишних слэшей

Прежде чем передавать строки путей в методы НайтиФайлы(), СоздатьКаталог() или Записать(), необходимо убедиться, что они не содержат обратных слэшей \ (в FTP используются только прямые слэши /) и не заканчиваются слэшем. Напишем простую вспомогательную функцию:


Функция НормализоватьПутьFTP(Знач ВходнойПуть) Экспорт
    // Заменяем все обратные слэши на прямые
    Результат = СтрЗаменить(ВходнойПуть, "\", "/");
    
    // Удаляем случайные двойные слэши
    Пока Найти(Результат, "//") > 0 Цикл
        Результат = СтрЗаменить(Результат, "//", "/");
    КонецЦикла;
    
    // Удаляем завершающий слэш, если он есть
    Если Прав(Результат, 1) = "/" Тогда
        Результат = Сред(Результат, 1, СтрДлина(Результат) - 1);
    КонецЕсли;
    
    Возврат Результат;
КонецФункции

Шаг 2. Обязательное использование Пассивного режима

В 90% случаев проблемы с зависанием поиска файлов или неожиданными ошибками «Access denied» на современных серверах решаются принудительной установкой пассивного режима соединения. В пассивном режиме клиент (1С) сам инициирует порты для передачи данных, что позволяет успешно обходить ограничения брандмауэров и NAT у провайдера.


// Инициализация соединения с пассивным режимом (6-й параметр равен Истина)
Соединение = Новый FTPСоединение(ФТПХост, ФТППорт, ФТППользователь, ФТППароль, , Истина);

Шаг 3. Реализация рекурсивного создания каталогов

Поскольку стандартный метод СоздатьКаталог() не умеет создавать вложенные цепочки папок за раз, напишем процедуру, которая будет безопасно и пошагово создавать всю структуру папок на сервере, предварительно проверяя их существование.


Процедура СоздатьКаталогНаСервереРекурсивно(Соединение, Знач ПолныйПутьНаСервере) Экспорт
    ПолныйПутьНаСервере = НормализоватьПутьFTP(ПолныйПутьНаСервере);
    ЧастиПути = СтрРазделить(ПолныйПутьНаСервере, "/");
    
    ТекущийПроверяемыйПуть = "";
    Для Каждого ЧастьПути Из ЧастиПути Цикл
        Если ПустаяСтрока(ЧастьПути) Тогда
            Продолжить;
        КонецЕсли;
        
        ТекущийПроверяемыйПуть = ТекущийПроверяемыйПуть + "/" + ЧастьПути;
        ТекущийПроверяемыйПуть = НормализоватьПутьFTP(ТекущийПроверяемыйПуть);
        
        // Проверяем существование каталога на этом уровне
        Попытка
            МассивФайлов = Соединение.НайтиФайлы(ТекущийПроверяемыйПуть, "");
            КаталогСуществует = (МассивФайлов.Количество() > 0);
        Исключение
            // Если НайтиФайлы упал с ошибкой, считаем, что каталога нет
            КаталогСуществует = Ложь;
        КонецПопытки;
        
        Если Не КаталогСуществует Тогда
            Попытка
                Соединение.СоздатьКаталог(ТекущийПроверяемыйПуть);
                Сообщить("Успешно создан каталог: " + ТекущийПроверяемыйПуть);
            Исключение
                // Ошибку игнорируем только если папка создалась параллельным процессом
                ВызватьИсключение "Не удалось создать каталог " + ТекущийПроверяемыйПуть + ". Причина: " + ОписаниеОшибки();
            КонецПопытки;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Интеграция решений в итоговый алгоритм выгрузки

Теперь объединим все наши наработки в единый и надежный алгоритм. Наша задача — выгрузить файлы из локального каталога D:\1С\webdata на FTP-сервер в папку upload/import_files, воссоздавая структуру папок и избегая переключения контекста через УстановитьТекущийКаталог.


Процедура ВыполнитьВыгрузкуНаFTP()
    // 1. Инициализация параметров
    ФТПХост = "185.41.162.114";
    ФТППорт = 21;
    ФТППользователь = "admin";
    ФТППароль = "password";
    
    ЛокальнаяПапкаВыгрузки = "D:\1С\webdata";
    БазовыйКаталогНаFTP = "upload/import_files";
    
    // 2. Устанавливаем соединение в пассивном режиме
    Попытка
        Соединение = Новый FTPСоединение(ФТПХост, ФТППорт, ФТППользователь, ФТППароль, , Истина);
        Сообщить("Соединение с FTP-сервером успешно установлено.");
    Исключение
        Сообщить("Не удалось подключиться к FTP-серверу: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    
    // 3. Получаем список локальных файлов для выгрузки (включая подкаталоги)
    ЛокальныеФайлы = НайтиФайлы(ЛокальнаяПапкаВыгрузки, "*", Истина);
    
    Для Каждого ФайлИмя Из ЛокальныеФайлы Цикл
        // Вычисляем относительный путь файла для воссоздания структуры на FTP
        ОтносительныйПуть = СтрЗаменить(ФайлИмя.Путь, ЛокальнаяПапкаВыгрузки, "");
        ОтносительныйПуть = НормализоватьПутьFTP(ОтносительныйПуть);
        
        // Формируем целевой путь каталога на FTP-сервере
        ЦелевойКаталогFTP = БазовыйКаталогНаFTP + "/" + ОтносительныйПуть;
        ЦелевойКаталогFTP = НормализоватьПутьFTP(ЦелевойКаталогFTP);
        
        Если ФайлИмя.ЭтоКаталог() Тогда
            // Если это каталог — создаем его рекурсивно на сервере
            Попытка
                СоздатьКаталогНаСервереРекурсивно(Соединение, ЦелевойКаталогFTP + "/" + ФайлИмя.Имя);
            Исключение
                Сообщить("Ошибка при создании папки: " + ОписаниеОшибки());
            КонецПопытки;
        Иначе
            // Если это файл — сначала убедимся, что папка для него создана
            Попытка
                СоздатьКаталогНаСервереРекурсивно(Соединение, ЦелевойКаталогFTP);
                
                // Записываем файл напрямую, указывая полный путь назначения на FTP
                ИмяФайлаНаСервере = ЦелевойКаталогFTP + "/" + ФайлИмя.Имя;
                ИмяФайлаНаСервере = НормализоватьПутьFTP(ИмяФайлаНаСервере);
                
                Соединение.Записать(ФайлИмя.ПолноеИмя, ИмяФайлаНаСервере);
                Сообщить("Успешно выгружен файл: " + ИмяФайлаНаСервере);
            Исключение
                Сообщить("Ошибка при выгрузке файла " + ФайлИмя.Имя + ": " + ОписаниеОшибки());
            КонецПопытки;
        КонецЕсли;
    КонецЦикла;
    
    Сообщить("Процесс выгрузки успешно завершен.");
КонецПроцедуры

Заключение

Мы подробно разобрали особенности работы платформы 1С с протоколом FTP и выяснили, что большинство ошибок вызваны мелкими деталями форматирования путей и ограничениями самих FTP-серверов. Использование пассивного режима, предварительная очистка путей от конечных слэшей и отказ от постоянной смены текущего каталога в пользу прямой абсолютной адресации гарантируют стабильную и бесперебойную работу ваших интеграционных механизмов.

← На главную