Ниже я расскажу как решил эту проблему с помощью 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/
Добавить комментарий