Двухколёсная автоматизация загрузки файлов на сервер из Notepad++

от автора

Так уж вышло, что по работе, мне приходится редактировать файлы, к которым я имею доступ только через файловый менеджер CMS Bitrix, что влечёт за собой открытие множества вкладок в браузере и огромное количество ненужных телодвижений необходимых лишь для того, чтобы отредактировать несколько файлов.
Ниже я расскажу как решил эту проблему с помощью Node.js и свободного времени.

Первое что я сделал — это узнал, как вообще битрикс подходит к редактированию файлов. Всё оказалось довольно просто и ничуть не превзошло мои ожидания — текст файла отправляется формой на сервер вместе с именем файла, где php скрипт открывает указанный файл и записывает в него содержимое.
Самое логичное, что пришло мне в голову — имитировать отправку формы на тот же обработчик.
Для этого нужно сделать 3 вещи:

  • Притвориться браузером
  • Притвориться пользователем у которого есть права на редактирование файлов
  • Отправить форму

Каждый, кто хоть немного знает о http протоколе знает что за определение клиентской программы отвечает строка User-Agent в заголовке запроса.
Вот именно здесь и следует указать сигнатуру программы, под которую мы маскируемся. У меня это:

Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0

Чуть сложнее дело обстоит с выдачей себя за пользователя. Методом тыка, а точнее поочерёдным удалением кук, было установлено, что для того что сайт считал пользователя вошедшим — ему необходимы лишь его логин и актуальный phpsessid. Логин я и так знаю, а вот номер сессии узнать можно несколькими способами.
Во-первых, можно просто посмотреть его с помощью «Инструментов разработчика» доступных в любом браузере.
Во-вторых, раз уж мы отправляем форму — можно перед этим отправлять форму входа на сайт. чтобы он номер сессии пришёл нам в виде куки.
Учитывая, что в любом случае загрузка файлов связана со входом на сайт, я решил воспользоваться первым способом, однако вместо того чтобы каждый раз открывать FireBug, просто добавил вывод phpsessid на одну из страниц сайта.
Последнее, и главное, что нам необходимо сделать — это отправить данные на сервер, как-будто бы из формы.
Здесь тоже нет ничего сложного. Во всё том же заголовке запроса указываем Content-Type, который определяет тип прилагаемого к заголовку контента, и добавляем Content-Length, что необходимо для того чтобы сервер знал сколько контента надо принять.
После этого в теле запроса посылаем сериализованные данные, которыми являются текст файла, его имя на сервере и некоторые другие переменные необходимые обработчику формы.
Теперь, когда «расчёты» окончены, можно переходить к практике.
Для начала определим — какая вообще информация необходима для успешной загрузки файлов на сервер. Кроме указанных логина и номера сессии необходим путь к файлам на локальной машине и путь на сервере, куда эти файлы будут сохраняться.
Первый было решено передавать скрипту как аргумент, а всю остальную информацию хранить в файле в той папке, которая передаётся скрипту.
В nodejs аргументы командной строки хранятся в поле argv глобальной переменной process, причем, даже если аргументы в программу не передаются — в массиве process.argv всёравно лежат 2 строки — «node» и имя выполняемой программы, поэтому первый аргумент лежит по смещению — 2. Сначала проверяем, передан ли хоть какой-то аргумент нашему скрипту, и если его вдруг не оказалось — выходим.

if(process.argv.length<3){     console.log("Необходим один аргумент!");     process.exit(1); } 

Получив путь до локальной папки, открываем файл config.json который должен там лежать.
За работу с файлми в nodejs отвечает модуль fs (FileSystem). Модули в nodejs это по сути просто наборы функций, однако все мы знаем что функции в javascript не совсем такие как в других языках.
Нам же понадобится вполне заурядная функция fs.readFile(path, callback) которая, как вы и сами можете догадаться — считывает содержимое файла. Если с первым её аргументм всё понятно, то второй — это функция, которая будет выполнена, когда файл будет полностью прочтён. Причём прогамма не замрёт в ожидании этого момента, а продолжит своё выполнение. Такой подход к организации программы называется «Событийно-ориентированное программирование». Итак — читаем файл настроек в указанной папке. Его содержимое будет передано в callback функцию вторым аргументом. Первым же, будет ошибка или false если всё нормально.

var fs = require('fs'); // говорим, что будем использовать модуль fs fs.readFile(process.argv[2]+'\config.json', function (err, data) {     if (err) throw err;     c = JSON.parse(data);// получаем информацию из json файла и записываем в глобальную переменную     //...что-то с этими данными делаем }); 

Далее необходимо получить список файлов в папке и каждый из них загрузить на сервер, если только этот файл не является файлом конфигурации или другим игнорируемым файлом.

//...внутри предыдущей функции fs.readdir(process.argv[2],function(err,files){ // берём список файлов в папке     if (err) throw err;     files.forEach(function(el,index,array){          if(el.split('.').length>1)         if(el!='config.json' && el!='.git' && el!='.gitattributes' && el!='.gitignore') // если файл подходит             upload(el); // загружаем его на сервер     }); }); 

Получив имя файла, мы можем прочитать его содержимое и послать его на сервер. Именно это мы и сделаем. Для того чтобы послать http запрос в nodejs есть модуль http. Подключив его мы можем вызвать функцию http.request(method, path, port, hostname, headers, callback), где предпоследний аргумент буквально делает нас всемогущими, ведь он позволяет отправить запрос с каким угодно заголовком, именно здесь мы укажем User-Agent, куки и тип контента запроса. Делая запрос мы получаем его в переменную, через которую можем влиять на его поведение. Например методы write() и end(). Отправляют данные в теле запроса с той лишь разницей что write можно вызвать несколько раз подряд, а end — только в один раз т.к. сразу после его вызова последует завершение запроса.
Как было сказано ранее — данные необходимо отправлять в сериализованном виде, а именно — так как мы бы из видели в адресной строке. В nodejs есть модуль для работы с такими данными, и называется он — querystring. Он позволяет сохранять объект в строку и наоборот. Все данные формы у нас хранятся в глобальной переменной query, которую мы и сериализуем для последующей отправки.

var http = require('http'); var querystring = require('querystring'); function upload(file){     console.log("Загрузка файла %s:",file);     var fileData=fs.readFileSync(process.argv[2]+'\\'+file); // получаем содержимое файла     query.path=c.path+file;// указываем в форме имя отправляемого файла      query.filesrc=fileData.toString();     var str=querystring.stringify(query);// сериализуем данные формы     var len=str.length;     var request = http.request( // выполняем запрос     {         method:'post',         path:'/bitrix/admin/fileman_file_edit.php',         port:80,         hostname:'***.****.ru',         headers:{             'host':'***.****.ru',             'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0',              'Cookie':'PHPSESSID='+c.phpsessid+';BITRIX_SM_LOGIN='+c.login,             'Content-Type':'application/x-www-form-urlencoded',             'Content-Length':len         }     },rListener); // указываем функцию которая будет вызвана когда придёт ответ     request.end(str); // посылаем тело запроса (сериализованную форму) и завершаем его } 

Ну и для того чтобы знать — загружен ли наш файл или труды наши были напрасны — принимаем ответ на запрос. Callback вызываемый при получении ответа, первым аргументом принимает всю информацию ответа, в том числе и статус. Всё тем же методом тыка, было обнаружено, что при успешной загрузке файла в ответ приходит редирект, в остальных же случаях в ответ приходит страница.

function rListener(response){     var status='';     switch(response.statusCode){         case 302: status='Файл загружен';break; // если пришёл редирект - всё ОК         case 200: status='Ваша сессия просрочена';break;         default : status='Ошибка';     }     console.log('%s (%d).',status,response.statusCode); } 

Полный код скрипта лежит здесь.
Имея такой вот велосипед, довольно просто настроить вызов скрипта по нажатию клавиши в вашем любимом редакторе. У меня это Notepad++ с плагином NppExec.

ссылка на оригинал статьи http://habrahabr.ru/post/190272/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *