// OneScript: ./OInt/core/Modules/OPI_SFTP.os // Lib: SFTP // CLI: sftp // Keywords: sftp // MIT License // Copyright (c) 2023-2025 Anton Tsitavets // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // https://github.com/Bayselonarrend/OpenIntegrations // BSLLS:Typo-off // BSLLS:LatinAndCyrillicSymbolInWord-off // BSLLS:IncorrectLineBreak-off // BSLLS:NumberOfOptionalParams-off // BSLLS:UsingServiceTag-off // BSLLS:LineLength-off // BSLLS:UsingSynchronousCalls-off //@skip-check module-structure-top-region //@skip-check module-structure-method-in-regions //@skip-check wrong-string-literal-content //@skip-check method-too-many-params //@skip-check constructor-function-return-section #Область ПрограммныйИнтерфейс #Область ОсновныеМетоды // Открыть соединение !NOCLI // Создает новый сеанс SFTP // // Примечание: // Получить конфигурацию соединения можно при помощи функций `ПолучитьНастройкиЛогинПароль`,^^ // `ПолучитьНастройкиПриватныйКлюч`, `ПолучитьНастройкиЧерезАгента` // // Параметры: // НастройкиSSH - Структура Из КлючИЗначение - Структура настроек соединения - set // Прокси - Структура Из КлючИЗначение - Структура настроек прокси, если необходимо - proxy // // Возвращаемое значение: // Произвольный, Соответствие Из КлючИЗначение - Открыть соединение Функция ОткрытьСоединение(Знач НастройкиSSH, Знач Прокси = "") Экспорт СоединениеSSH = OPI_ЗапросыSSH.ОткрытьСоединение(НастройкиSSH, Прокси); Если ЭтоКоннектор(СоединениеSSH) Тогда Результат = СоединениеSSH.ToSFTP(); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); Иначе Возврат СоединениеSSH; КонецЕсли; Возврат ?(Результат["result"], СоединениеSSH, Результат); КонецФункции // Получить конфигурацию соединения // Формирует полную структуру настроек соединения, которая может быть использована вместо самого соединения при вызове других функций // // Примечание: // Может быть передана в качестве параметра `Соединение` в других функциях вместо настоящего соединения из функции `ОткрытьСоединение`.^^ // При этом новое соединение будет открыто и закрыто в рамках вызываемой функции // Не рекомендуется использовать конфигурацию соединения при множественных обращениях к серверу SSH.^^ // Данный функционал предназначен, в первую очередь для CLI версии ОПИ, где хранение соединения между вызовами невозможно // // Параметры: // НастройкиSSH - Структура Из КлючИЗначение - Настройки SSH - set // Прокси - Структура Из КлючИЗначение - Настройки прокси, если необходимо. См. ПолучитьНастройкиПрокси - proxy // // Возвращаемое значение: // Структура Из КлючИЗначение - Структура настроек соединения Функция ПолучитьКонфигурациюСоединения(Знач НастройкиSSH, Знач Прокси = Неопределено) Экспорт Возврат OPI_ЗапросыSSH.ПолучитьКонфигурациюСоединения(НастройкиSSH, Прокси); КонецФункции // Закрыть соединение !NOCLI // Явно закрывает переданное соединение // // Параметры: // Соединение - Произвольный - Объект компоненты с открытым соединением - conn // // Возвращаемое значение: // Структура Из КлючИЗначение - Результат закрытия соединения Функция ЗакрытьСоединение(Знач Соединение) Экспорт Возврат OPI_ЗапросыSSH.ЗакрытьСоединение(Соединение); КонецФункции // Изменить путь // Изменяет путь объекта на указанный // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Текущий путь к объекту - old // НовыйПуть - Строка - Новый путь к объекту - new // Перезаписывать - Булево - Перезаписывать, если объект по целевому пути уже существует - rw // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция ИзменитьПуть(Знач Соединение, Знач Путь, Знач НовыйПуть, Знач Перезаписывать = Ложь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); OPI_ПреобразованиеТипов.ПолучитьСтроку(НовыйПуть); OPI_ПреобразованиеТипов.ПолучитьБулево(Перезаписывать); Результат = Соединение.RenameObject(Путь, НовыйПуть, Перезаписывать); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // Это коннектор !NOCLI // Проверяет, что значение является объектом внешней компоненты для работы с SFTP // // Параметры: // Значение - Произвольный - Значение для проверки - value // // Возвращаемое значение: // Булево - Это коннектор Функция ЭтоКоннектор(Знач Значение) Экспорт Возврат OPI_ЗапросыSSH.ЭтоКоннектор(Значение); КонецФункции // Получить настройки (логин/пароль) // Получает настройки соединения с авторизацией через логин и пароль // // Параметры: // Хост - Строка - Хост SSH - host // Порт - Число - Порт SSH - port // Логин - Строка - Имя пользователя SSH - user // Пароль - Строка - Пароль пользователя SSH - pass // // Возвращаемое значение: // Структура Из КлючИЗначение - Конфигурация соединения Функция ПолучитьНастройкиЛогинПароль(Знач Хост, Знач Порт, Знач Логин, Знач Пароль = "") Экспорт Возврат OPI_ЗапросыSSH.ПолучитьНастройкиЛогинПароль(Хост, Порт, Логин, Пароль); КонецФункции // Получить настройки (приватный ключ) // Получает настройки соединения с авторизацией через приватный ключ // // Параметры: // Хост - Строка - Хост SSH - host // Порт - Число - Порт SSH - port // Логин - Строка - Имя пользователя SSH - user // Приватный - Строка - Путь к файлу приватного ключа - key // Публичный - Строка - Путь к файлу публичного ключ - pub // Пароль - Строка - Пароль (passphrase) приватного ключа - pass // // Возвращаемое значение: // Структура Из КлючИЗначение - Конфигурация соединения Функция ПолучитьНастройкиПриватныйКлюч(Знач Хост , Знач Порт , Знач Логин , Знач Приватный , Знач Публичный = "" , Знач Пароль = "") Экспорт Возврат OPI_ЗапросыSSH.ПолучитьНастройкиПриватныйКлюч(Хост, Порт, Логин, Приватный, Публичный, Пароль); КонецФункции // Получить настройки (через агента) // Получает настройки соединения с авторизацией через SSH Agent // // Параметры: // Хост - Строка - Хост SSH - host // Порт - Число - Порт SSH - port // Логин - Строка - Имя пользователя SSH - user // // Возвращаемое значение: // Структура Из КлючИЗначение - Конфигурация соединения Функция ПолучитьНастройкиЧерезАгента(Знач Хост, Знач Порт, Знач Логин) Экспорт Возврат OPI_ЗапросыSSH.ПолучитьНастройкиЧерезАгента(Хост, Порт, Логин); КонецФункции // Получить настройки прокси // Формирует структуру настроек прокси-сервера для соединения // // Параметры: // Адрес - Строка - Адрес прокси - addr // Порт - Число - Порт прокси - port // Вид - Строка - Вид прокси: socks5, socks4, http - type // Логин - Строка, Неопределено - Логин авторизации, если необходимо - login // Пароль - Строка, Неопределено - Пароль для авторизации, если необходимо - pass // // Возвращаемое значение: // Структура Из КлючИЗначение - Структура настроек прокси Функция ПолучитьНастройкиПрокси(Знач Адрес , Знач Порт , Знач Вид = "socks5" , Знач Логин = Неопределено , Знач Пароль = Неопределено) Экспорт //@skip-check constructor-function-return-section Возврат OPI_Компоненты.ПолучитьНастройкиПрокси(Адрес, Порт, Вид, Логин, Пароль); КонецФункции #КонецОбласти #Область РаботаСДиректориями // Получить список объектов // Получает информацию о содержимом директории по выбранному пути // // Примечание: // Команда FTP: `LIST` // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь к директории поиска - path // Рекурсивно - Булево - Получать информацию об объектах во вложенных директориях - rcv // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция ПолучитьСписокОбъектов(Знач Соединение, Знач Путь = "", Знач Рекурсивно = Ложь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьБулево(Рекурсивно); OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); Результат = Соединение.ListDirectory(Путь); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); ОбработатьРезультатПолученияСписка(Результат, Соединение, Рекурсивно); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // BSLLS:MagicNumber-off // Создать новую директорию // Создает директорию по указанному пути // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь к новой директории - path // Права - Строка - Режим доступа к каталогу в числовом формате POSIX (как в chmod) - mode // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция СоздатьНовуюДиректорию(Знач Соединение, Знач Путь, Знач Права = 700) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); Результат = Соединение.MakeDirectory(Путь, Права); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // BSLLS:MagicNumber-on // Удалить директорию // Удаляет существующую директорию // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь к удаляемой директории - path // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция УдалитьДиректорию(Знач Соединение, Знач Путь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); Результат = Соединение.RemoveDirectory(Путь); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // Получить текущий каталог // Получает текущий каталог, от которого вычисляются относительные пути // // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция ПолучитьТекущийКаталог(Знач Соединение) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе Результат = OPI_ЗапросыSSH.ВыполнитьКоманду(Соединение, "pwd"); ОбработатьВыполнениеКомандыSSH(Результат, "path"); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции #КонецОбласти #Область РаботаСФайлами // Загрузить файл // Загружает файл с диска или двоичные данные на сервер // // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Файл - Строка, ДвоичныеДанные - Файл на диске или данные файла - file // Путь - Строка - Путь сохранения файла на сервере - path // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция ЗагрузитьФайл(Знач Соединение, Знач Файл, Знач Путь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе Если ТипЗнч(Файл) = Тип("Строка") Тогда ФайлНаДиске = Новый Файл(Файл); ЭтоФайлНаДиске = ФайлНаДиске.Существует(); Иначе ЭтоФайлНаДиске = Ложь; КонецЕсли; Если Не ЭтоФайлНаДиске Тогда OPI_ПреобразованиеТипов.ПолучитьДвоичныеДанные(Файл); Если OPI_Компоненты.ТребуетсяПередачаЧерезФайл() Тогда // BSLLS:MissingTemporaryFileDeletion-off //@skip-check missing-temporary-file-deletion ИВФ = ПолучитьИмяВременногоФайла(); // BSLLS:MissingTemporaryFileDeletion-on Файл.Записать(ИВФ); Результат = Соединение.UploadFile(ИВФ, Путь); OPI_Инструменты.УдалитьФайлВПопытке(ИВФ, "Не удалось удалить временный файл после загрузки"); Иначе Результат = Соединение.UploadData(Файл, Путь); КонецЕсли; Иначе Результат = Соединение.UploadFile(Файл, Путь); КонецЕсли; Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // Удалить файл // Удаляет файл с сервера // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь файла на сервере - path // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция УдалитьФайл(Знач Соединение, Знач Путь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); Результат = Соединение.RemoveFile(Путь); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // Сохранить файл // Сохраняет файл с сервера по указанному пути // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь файла на сервере - path // ИмяФайла - Строка - Путь для сохранения файла на диске - file // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция СохранитьФайл(Знач Соединение, Знач Путь, Знач ИмяФайла) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); OPI_ПреобразованиеТипов.ПолучитьСтроку(ИмяФайла); OPI_Инструменты.ВернутьУправляющиеПоследовательности(ИмяФайла); Результат = Соединение.DownloadToFile(Путь, ИмяФайла); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // Получить данные файла !NOCLI // Получает файл с сервера как двоичные данные // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь файла на сервере - path // // Возвращаемое значение: // Соответствие Из КлючИЗначение, ДвоичныеДанные - Данные файла или информация об ошибке Функция ПолучитьДанныеФайла(Знач Соединение, Знач Путь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); Если OPI_Компоненты.ТребуетсяПередачаЧерезФайл() Тогда // BSLLS:MissingTemporaryFileDeletion-off //@skip-check missing-temporary-file-deletion ИВФ = ПолучитьИмяВременногоФайла(); // BSLLS:MissingTemporaryFileDeletion-on Результат = Соединение.DownloadToFile(Путь, ИВФ); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); Если Результат["result"] Тогда Данные = Новый ДвоичныеДанные(ИВФ); OPI_Инструменты.УдалитьФайлВПопытке(ИВФ, "Не удалось удалить временный файл после загрузки"); Возврат Данные; КонецЕсли; Иначе Данные = Соединение.DownloadToBuffer(Путь); Если ТипЗнч(Данные) = Тип("Строка") Тогда Результат = OPI_Инструменты.JsonВСтруктуру(Данные); Иначе Возврат Данные; КонецЕсли; КонецЕсли; КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции // Получить информацию о файле // Получает информацию о файле по указанному пути // // Параметры: // Соединение - Произвольный - Существующее соединение или конфигурация соединения - conn // Путь - Строка - Путь файла на сервере - path // // Возвращаемое значение: // Соответствие Из КлючИЗначение - Результат обработки Функция ПолучитьИнформациюОФайле(Знач Соединение, Знач Путь) Экспорт ЗакрыватьСоединение = ПроверитьСоздатьСоединение(Соединение); Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; Иначе OPI_ПреобразованиеТипов.ПолучитьСтроку(Путь); Результат = Соединение.GetFileInfo(Путь); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); КонецЕсли; Если ЗакрыватьСоединение Тогда Результат.Вставить("close_connection", ЗакрытьСоединение(Соединение)); КонецЕсли; Возврат Результат; КонецФункции #КонецОбласти #КонецОбласти #Область СлужебныеПроцедурыИФункции Функция ПроверитьСоздатьСоединение(Соединение) Если Не ЭтоКоннектор(Соединение) Тогда ЗакрыватьСоединение = Истина; Соединение = ОткрытьСоединениеПоКонфигурации(Соединение); Иначе ЗакрыватьСоединение = Ложь; КонецЕсли; Если Не ЭтоКоннектор(Соединение) Тогда Возврат Соединение; КонецЕсли; ЭтоSFTP = Соединение.IsSFTP(); Если Не ЭтоSFTP Тогда Результат = Соединение.ToSFTP(); Результат = OPI_Инструменты.JsonВСтруктуру(Результат); Соединение = ?(Результат["result"], Соединение, Результат); КонецЕсли; Возврат ЗакрыватьСоединение; КонецФункции Функция ОткрытьСоединениеПоКонфигурации(Знач СтруктураКонфигурации) Если ЭтоКоннектор(СтруктураКонфигурации) Тогда Возврат СтруктураКонфигурации; КонецЕсли; ШаблонОшибки = "Передана некорректная конфигурация соединения: %1"; Попытка OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(СтруктураКонфигурации); Исключение Результат = Новый Соответствие; Результат.Вставить("result", Ложь); Результат.Вставить("error" , СтрШаблон(ШаблонОшибки, ОписаниеОшибки())); Возврат Результат; КонецПопытки; Если Не OPI_Инструменты.ПолеКоллекцииСуществует(СтруктураКонфигурации, "set") Тогда Результат = Новый Соответствие; Результат.Вставить("result", Ложь); Результат.Вставить("error" , СтрШаблон(ШаблонОшибки, "отсутствуют основные параметры соединения")); Возврат Результат; КонецЕсли; НастройкиSSH = СтруктураКонфигурации["set"]; Прокси = OPI_Инструменты.ПолучитьИли(СтруктураКонфигурации, "proxy", Неопределено); Возврат ОткрытьСоединение(НастройкиSSH, Прокси); КонецФункции Процедура ОбработатьВыполнениеКомандыSSH(Результат, ПолеРезультата = "data") Result_ = "result"; Если Результат[Result_] Тогда РезультатОбработки = Новый Соответствие; Если Не ЗначениеЗаполнено(Результат["stderr"]) Тогда РезультатОбработки.Вставить(Result_, Истина); StdOut = Результат["stdout"]; Если ЗначениеЗаполнено(StdOut) Тогда РезультатОбработки.Вставить(ПолеРезультата, StdOut); КонецЕсли; Иначе РезультатОбработки.Вставить(Result_, Ложь); РезультатОбработки.Вставить("error", Результат["stderr"]); КонецЕсли; Результат = РезультатОбработки; КонецЕсли; КонецПроцедуры Процедура ОбработатьРезультатПолученияСписка(Результат, Соединение, Знач Рекурсивно) Если Результат["result"] И Рекурсивно Тогда Для Каждого ОбъектКаталога Из Результат["data"] Цикл Если Не ОбъектКаталога["is_directory"] Тогда Продолжить; КонецЕсли; Дочерние = ПолучитьСписокОбъектов(Соединение, ОбъектКаталога["path"], Истина); Если Дочерние["result"] Тогда ОбъектКаталога.Вставить("objects", Дочерние["data"]); Иначе ОбъектКаталога.Вставить("objects", Дочерние["error"]); КонецЕсли; КонецЦикла; КонецЕсли; КонецПроцедуры #КонецОбласти