Это командный перфильтр, который можно запустить в бесплатном хостинг-провайдере OnWorks, используя одну из наших многочисленных бесплатных онлайн-рабочих станций, таких как Ubuntu Online, Fedora Online, онлайн-эмулятор Windows или онлайн-эмулятор MAC OS.
ПРОГРАММА:
ИМЯ
perlfilter - Исходные фильтры
ОПИСАНИЕ
Эта статья посвящена малоизвестной функции Perl, называемой источник фильтры. Исходные фильтры
изменить программный текст модуля до того, как Perl его увидит, так же, как препроцессор C изменяет
исходный текст программы C до того, как его увидит компилятор. Эта статья расскажет вам больше
о том, что такое исходные фильтры, как они работают и как написать свой собственный.
Первоначальная цель фильтров исходных текстов заключалась в том, чтобы позволить вам зашифровать исходный текст вашей программы для
предотвратить случайное пиратство. Как вы скоро узнаете, это не все, что они могут делать. Но сначала
основы.
КОНЦЕПЦИИ
Прежде чем интерпретатор Perl сможет выполнить сценарий Perl, он должен сначала прочитать его из файла.
в память для разбора и компиляции. Если этот сценарий сам включает другие сценарии с
оператор "использовать" или "требовать", тогда каждый из этих сценариев должен быть прочитан из их
соответствующие файлы.
Теперь подумайте о каждой логической связи между парсером Perl и отдельным файлом как о
источник поток. Исходный поток создается, когда парсер Perl открывает файл, он продолжает
существовать, поскольку исходный код считывается в память, и он уничтожается, когда Perl завершает работу
разбор файла. Если синтаксический анализатор обнаруживает в источнике оператор "require" или "use"
stream, новый и отдельный поток создается только для этого файла.
На диаграмме ниже представлен поток с одним исходным кодом с потоком исходного кода из Perl.
файл сценария слева в парсер Perl справа. Вот как обычно Perl
работает.
файл -------> парсер
Следует помнить два важных момента:
1. Хотя в любой момент времени может существовать любое количество исходных потоков,
только один будет активен.
2. Каждый исходный поток связан только с одним файлом.
Фильтр источника - это особый вид модуля Perl, который перехватывает и изменяет исходный код.
поток прежде, чем он достигнет парсера. Исходный фильтр изменяет нашу диаграмму следующим образом:
файл ----> фильтр ----> парсер
Если в этом нет особого смысла, рассмотрим аналогию с командным конвейером. Скажите, что у вас есть
сценарий оболочки, хранящийся в сжатом файле try.gz. Простая команда конвейера ниже
запускает сценарий без необходимости создания временного файла для хранения несжатого файла.
gunzip -c trial.gz | ш
В этом случае поток данных из конвейера можно представить следующим образом:
try.gz ----> gunzip ----> sh
С помощью фильтров источника вы можете сохранить текст вашего скрипта в сжатом виде и использовать источник
фильтр, чтобы распаковать его для парсера Perl:
сжатый архив
Программа Perl ---> исходный фильтр ---> парсер
С ПОМОЩЬЮ ФИЛЬТРЫ
Так как же использовать исходный фильтр в сценарии Perl? Выше я сказал, что исходный фильтр
просто особый вид модуля. Как и все модули Perl, исходный фильтр вызывается с
заявление об использовании.
Допустим, вы хотите передать исходный код Perl через препроцессор C перед выполнением. Как это
происходит, дистрибутив исходных фильтров поставляется с модулем фильтра препроцессора C, который называется
Фильтр :: cpp.
Ниже приведен пример программы cpp_test, в которой используется этот фильтр. Номера строк
были добавлены, чтобы можно было легко ссылаться на определенные строки.
1: используйте Filter :: cpp;
2: #define ИСТИНА 1
3: $ a = ИСТИНА;
4: выведите «a = $ a \ n»;
Когда вы выполняете этот сценарий, Perl создает исходный поток для файла. Перед парсером
обрабатывает любую из строк файла, исходный поток выглядит так:
cpp_test ---------> парсер
Строка 1, «use Filter :: cpp», включает и устанавливает модуль фильтра «cpp». Все исходники
фильтры работают так. Оператор использования компилируется и выполняется во время компиляции, прежде чем
читается какая-либо часть файла, и он присоединяет фильтр cpp к исходному потоку позади
сцены. Теперь поток данных выглядит так:
cpp_test ----> cpp фильтр ----> парсер
Когда парсер считывает вторую и последующие строки из исходного потока, он передает эти
строки через исходный фильтр "cpp" перед их обработкой. Фильтр "cpp" просто
пропускает каждую строку через настоящий препроцессор C. Выходные данные препроцессора C:
затем вставляется обратно в исходный поток фильтром.
.-> cpp -.
| |
| |
| <- '
cpp_test ----> cpp фильтр ----> парсер
Затем парсер видит следующий код:
используйте Filter :: cpp;
$ a = 1;
напечатайте "a = $ a \ n";
Давайте посмотрим, что происходит, когда отфильтрованный код включает другой модуль с использованием:
1: используйте Filter :: cpp;
2: #define ИСТИНА 1
3: используйте Фреда;
4: $ a = ИСТИНА;
5: выведите «a = $ a \ n»;
Фильтр «cpp» не применяется к тексту модуля Fred, только к тексту
файл, который его использовал ("cpp_test"). Хотя оператор использования в строке 3 будет проходить через
cpp фильтр, включенный модуль ("Фред") не будет. Исходные потоки выглядят как
это после анализа строки 3 и до анализа строки 4:
cpp_test ---> cpp filter ---> parser (НЕАКТИВНО)
Fred.pm ----> парсер
Как видите, создан новый поток для чтения источника из "Fred.pm". Этот
поток будет оставаться активным до тех пор, пока не будет проанализирован весь "Fred.pm". Исходный поток для
"cpp_test" все еще существует, но неактивен. Как только парсер закончит читать
Fred.pm связанный с ним исходный поток будет уничтожен. Исходный поток для
Затем "cpp_test" снова становится активным, и синтаксический анализатор читает строку 4 и последующие строки из
"cpp_test".
Вы можете использовать более одного исходного фильтра для одного файла. Точно так же вы можете повторно использовать
тот же фильтр в любом количестве файлов.
Например, если у вас есть закодированный и сжатый исходный файл, можно сложить
фильтр uudecode и такой фильтр распаковки:
используйте Filter :: uudecode; используйте Filter :: uncompress;
M'XL(".H<US4''V9I;F%L')Q;>7/;1I;_>_I3=&E=%:F*I"T?22Q/
M6] 9 *
...
После обработки первой строки поток будет выглядеть так:
файл ---> uudecode ---> распаковать ---> парсер
фильтр фильтр
Данные проходят через фильтры в том же порядке, в котором они появляются в исходном файле. Кодекс
фильтр появился перед фильтром распаковки, поэтому исходный файл будет закодирован перед
он несжатый.
ПИСЬМО A ИСТОЧНИК ФИЛЬТР
Есть три способа написать собственный исходный фильтр. Вы можете написать это на C, используя
внешняя программа как фильтр, или напишите фильтр на Perl. Я не буду рассказывать о первых двух
любая важная деталь, так что я сначала уберу их с дороги. Написание фильтра на Perl - это
самый удобный, поэтому я уделю ему больше всего места.
ПИСЬМО A ИСТОЧНИК ФИЛЬТР IN C
Первый из трех доступных методов - полностью написать фильтр на C.
Внешний модуль, вы создаете интерфейсы напрямую с хуками исходных фильтров, предоставляемыми
Перл.
Преимущество этого метода в том, что у вас есть полный контроль над реализацией.
вашего фильтра. Большим недостатком является повышенная сложность, необходимая для написания
filter - вам нужно не только разбираться в хуках исходных фильтров, но и
разумное знание Perl. Один из немногих случаев, когда стоит пойти на эту неприятность
это при написании скремблера исходного кода. Фильтр «дешифровать» (который расшифровывает исходный код
перед тем, как Perl проанализирует его), включенный в дистрибутив исходного фильтра, является примером C
исходный фильтр (см. «Фильтры дешифрования» ниже).
дешифрование Фильтры
Все фильтры дешифрования работают по принципу «безопасность через неизвестность».
Независимо от того, насколько хорошо вы пишете фильтр дешифрования и насколько надежно ваше шифрование
алгоритм состоит в том, что любой достаточно решительный может получить исходный исходный код. В
причина довольно проста - как только фильтр дешифрования расшифровывает источник обратно в
его первоначальный вид, фрагменты будут храниться в памяти компьютера как Perl
разбирает это. Источник может находиться в памяти только в течение короткого периода времени, но кто угодно
наличие отладчика, навыков и большого терпения может в конечном итоге восстановить вашу
программу.
Тем не менее, есть ряд шагов, которые можно предпринять, чтобы усложнить жизнь
потенциальный взломщик. Самое важное: напишите свой фильтр дешифрования на C и
статически связать модуль дешифрования с двоичным кодом Perl. Для получения дополнительных советов сделать
жизнь трудная для потенциального взломщика, см. файл расшифровать.pm в источнике
распределение фильтров.
ФОРМИРОВАНИЕ A ИСТОЧНИК ФИЛЬТР AS A ОТДЕЛЬНЫЙ EXECUTABLE
Альтернативой написанию фильтра на C является создание отдельного исполняемого файла в
язык по вашему выбору. Отдельный исполняемый файл читает со стандартного ввода, делает все, что угодно.
обработка необходима, и отфильтрованные данные записываются в стандартный вывод. "Filter :: cpp" - это
пример исходного фильтра, реализованного как отдельный исполняемый файл - исполняемый файл является
Препроцессор C в комплекте с компилятором C.
В дистрибутив исходного фильтра входят два модуля, которые упрощают эту задачу:
«Фильтр :: exec» и «Фильтр :: sh». Оба позволяют запускать любой внешний исполняемый файл. Оба используют
сопроцесс для управления потоком данных во внешний исполняемый файл и из него. (Для
подробности о сопроцессах см. в Stephens, WR, "Advanced Programming in the UNIX
Окружающая среда ». Addison-Wesley, ISBN 0-210-56317-7, страницы 441-445.) Разница между
это то, что "Filter :: exec" напрямую порождает внешнюю команду, а "Filter :: sh"
порождает оболочку для выполнения внешней команды. (Unix использует оболочку Bourne; NT использует
cmd shell.) Создание оболочки позволяет использовать метасимволы оболочки и
средства перенаправления.
Вот пример сценария, который использует «Filter :: sh»:
используйте Filter :: sh 'tr XYZ PQR';
$ a = 1;
напечатайте "XYZ a = $ a \ n";
Результат, который вы получите при выполнении скрипта:
PQR а = 1
Написание исходного фильтра как отдельного исполняемого файла работает нормально, но небольшая производительность
штраф понесен. Например, если вы выполните небольшой пример выше, отдельный
будет создан подпроцесс для запуска команды Unix «tr». Каждое использование фильтра требует
собственный подпроцесс. Если создание подпроцессов стоит дорого в вашей системе, вы можете захотеть
рассмотреть один из других вариантов создания фильтров источника.
ПИСЬМО A ИСТОЧНИК ФИЛЬТР IN PERL
Самый простой и переносимый вариант создания собственного фильтра источника - это
напишите его полностью на Perl. Чтобы отличить этот метод от двух предыдущих, я
назовите это исходным фильтром Perl.
Чтобы понять, как написать исходный фильтр Perl, нам нужен пример для изучения. Вот это
полный фильтр источника, который выполняет декодирование rot13. (Rot13 - очень простое шифрование
Схема, используемая в сообщениях Usenet для сокрытия содержания оскорбительных сообщений. Он движется каждый
буквой вперед на тринадцать позиций, так что A становится N, B становится O, а Z становится M.)
пакет Rot13;
используйте Filter :: Util :: Call;
дополнительный импорт {
мой ($ type) = @_;
мой ($ ref) = [];
filter_add (благослови $ ref);
}
дополнительный фильтр {
мой ($ self) = @_;
мой ($ статус);
tr / n-za-mN-ZA-M / a-zA-Z /
если ($ status = filter_read ())> 0;
$ status;
}
1;
Все исходные фильтры Perl реализованы как классы Perl и имеют одинаковую базовую структуру.
как в примере выше.
Во-первых, мы включаем модуль «Filter :: Util :: Call», который экспортирует ряд функций.
в пространство имен вашего фильтра. Показанный выше фильтр использует две из этих функций:
filter_add () и filter_read ().
Затем мы создаем объект фильтра и связываем его с исходным потоком, определяя
функция "импорт". Если вы достаточно хорошо знаете Perl, вы знаете, что «импорт» называется
автоматически каждый раз, когда модуль включается с оператором использования. Это делает "импорт"
идеальное место как для создания, так и для установки объекта фильтра.
В примере фильтра объект ($ ref) благословляется так же, как и любой другой объект Perl. Наш
В примере используется анонимный массив, но это не является обязательным требованием. Поскольку этот пример
не нужно хранить какую-либо контекстную информацию, мы могли бы использовать скаляр или хеш
ссылка так же хорошо. В следующем разделе демонстрируются данные контекста.
Связь между объектом фильтра и исходным потоком осуществляется с помощью
Функция filter_add (). Это принимает объект фильтра в качестве параметра (в данном случае $ ref) и
устанавливает его в исходный поток.
Наконец, есть код, который фактически выполняет фильтрацию. Для этого типа исходного кода Perl
filter, вся фильтрация выполняется в методе filter (). (Также возможно
напишите исходный фильтр Perl, используя замыкание. См. Справочную страницу "Filter :: Util :: Call" для
подробнее.) Он вызывается каждый раз, когда парсеру Perl требуется другая строка исходного кода для
процесс. Метод filter (), в свою очередь, считывает строки из исходного потока с помощью
Функция filter_read ().
Если строка была доступна из исходного потока, filter_read () возвращает значение статуса.
больше нуля и добавляет строку к $ _. Нулевое значение состояния указывает на конец
файл меньше нуля означает ошибку. Ожидается, что сама функция фильтра вернет свой
status таким же образом и поместите отфильтрованную строку, которую он хочет записать, в исходный поток в
$ _. Использование $ _ объясняет краткость большинства исходных фильтров Perl.
Чтобы использовать фильтр rot13, нам нужен способ кодирования исходного файла в
формат rot13. Приведенный ниже сценарий, «mkrot13», делает именно это.
die "использование mkrot13 filename \ n", если только @ARGV;
мой $ in = $ ARGV [0];
мой $ out = "$ in.tmp";
open (IN, "<$ in") или die "Невозможно открыть файл $ in: $! \ n";
open (OUT, "> $ out") или die "Невозможно открыть файл $ out: $! \ n";
print OUT "использовать Rot13; \ n";
в то время как ( ) {
tr / a-zA-Z / n-za-mN-ZA-M /;
распечатка;
}
приближаться;
закрыть OUT;
отменить связь с $ in;
переименовать $ out, $ in;
Если мы зашифруем это с помощью «mkrot13»:
напечатайте "привет, Фред \ n";
результат будет такой:
используйте Rot13;
цеваг "урыыб серк \ а";
Его запуск приводит к следующему выводу:
привет Фред
С ПОМОЩЬЮ КОНТЕКСТ: ОТЛАЖИВАТЬ ФИЛЬТР
Пример rot13 был тривиальным примером. Вот еще одна демонстрация, демонстрирующая несколько
больше возможностей.
Скажем, вы хотите включить много отладочного кода в свой Perl-скрипт во время разработки,
но вы не хотели, чтобы это было в выпущенном продукте. Исходные фильтры предлагают решение.
Чтобы не усложнять пример, допустим, вы хотите, чтобы вывод отладки был
управляется переменной окружения "DEBUG". Код отладки включен, если переменная
существует, в противном случае он отключен.
Две специальные строки маркера будут заключать в скобки отладочный код, например:
## ОТЛАДКА_НАЧАЛО
if ($ year> 1999) {
warn "Отладка: ошибка тысячелетия в году $ year \ n";
}
## ОТЛАДКА_END
Фильтр гарантирует, что Perl анализирует код между и "DEBUG_END"
маркеры только тогда, когда существует переменная среды «DEBUG». Это означает, что когда "DEBUG"
существует, приведенный выше код должен быть пропущен через фильтр без изменений. Маркерные линии
также могут передаваться как есть, потому что парсер Perl увидит их как строки комментариев.
Когда «DEBUG» не установлен, нам нужен способ отключить отладочный код. Простой способ добиться
то есть преобразовать строки между двумя маркерами в комментарии:
## ОТЛАДКА_НАЧАЛО
#if ($ year> 1999) {
# предупреждение "Отладка: ошибка тысячелетия в году $ year \ n";
#}
## ОТЛАДКА_END
Вот полный фильтр отладки:
пакет Debug;
использовать строго;
использовать предупреждения;
используйте Filter :: Util :: Call;
используйте константу TRUE => 1;
используйте константу FALSE => 0;
дополнительный импорт {
мой ($ type) = @_;
мой (% context) = (
Включено => определено $ ENV {DEBUG},
InTraceBlock => ЛОЖЬ,
Имя файла => (вызывающий) [1],
LineNo => 0,
LastBegin => 0,
);
filter_add (благослови \% context);
}
суб умереть {
мой ($ self) = сдвиг;
мое ($ сообщение) = сдвиг;
мой ($ line_no) = сдвиг || $ self -> {LastBegin};
die "$ message at $ self -> {Filename} line $ line_no. \ n"
}
дополнительный фильтр {
мой ($ self) = @_;
мой ($ статус);
$ status = filter_read ();
++ $ self -> {LineNo};
# сначала обработайте EOF / ошибку
if ($ status <= 0) {
$ self-> Die ("DEBUG_BEGIN не имеет DEBUG_END")
если $ self -> {InTraceBlock};
вернуть $ status;
}
if ($ self -> {InTraceBlock}) {
if (/ ^ \ s * ## \ s * DEBUG_BEGIN /) {
$ self-> Die ("Вложенный DEBUG_BEGIN", $ self -> {LineNo})
} elsif (/ ^ \ s * ## \ s * DEBUG_END /) {
$ self -> {InTraceBlock} = ЛОЖЬ;
}
# закомментировать строки отладки, когда фильтр отключен
s / ^ / # / if! $ self -> {Включено};
} elsif (/ ^ \ s * ## \ s * DEBUG_BEGIN /) {
$ self -> {InTraceBlock} = ИСТИНА;
$ self -> {LastBegin} = $ self -> {LineNo};
} elsif (/ ^ \ s * ## \ s * DEBUG_END /) {
$ self-> Die («DEBUG_END не имеет DEBUG_BEGIN», $ self -> {LineNo});
}
вернуть $ status;
}
1;
Большая разница между этим фильтром и предыдущим примером заключается в использовании контекстных данных.
в объекте фильтра. Объект фильтра основан на ссылке на хэш и используется для сохранения
различные фрагменты контекстной информации между вызовами функции фильтра. Все, кроме двух из
хеш-поля используются для сообщений об ошибках. Первый из этих двух, Enabled, используется
фильтр, чтобы определить, следует ли передавать код отладки парсеру Perl. В
во-вторых, InTraceBlock, истинно, когда фильтр обнаружил строку «DEBUG_BEGIN», но
еще не обнаружил следующую строку "DEBUG_END".
Если вы проигнорируете все проверки ошибок, которые выполняет большая часть кода, суть фильтра
заключается в следующем:
дополнительный фильтр {
мой ($ self) = @_;
мой ($ статус);
$ status = filter_read ();
# сначала обработайте EOF / ошибку
вернуть $ status, если $ status <= 0;
if ($ self -> {InTraceBlock}) {
if (/ ^ \ s * ## \ s * DEBUG_END /) {
$ self -> {InTraceBlock} = ЛОЖЬ
}
# закомментировать строки отладки, когда фильтр отключен
s / ^ / # / if! $ self -> {Включено};
} elsif (/ ^ \ s * ## \ s * DEBUG_BEGIN /) {
$ self -> {InTraceBlock} = ИСТИНА;
}
вернуть $ status;
}
Будьте осторожны: точно так же, как препроцессор C не знает C, фильтр Debug не знает Perl.
Его довольно легко обмануть:
печать <
## DEBUG_BEGIN
МНВ
Если оставить в стороне такие вещи, вы можете увидеть, что многого можно достичь с помощью скромного количества кода.
Заключение
Теперь вы лучше понимаете, что такое исходный фильтр, и, возможно, у вас даже есть
возможное использование для них. Если вам хочется поиграть с исходными фильтрами, но нужно немного
Вдохновение, вот несколько дополнительных функций, которые вы можете добавить к фильтру отладки.
Во-первых, легкий. Вместо того, чтобы иметь отладочный код по принципу "все или ничего", это было бы
гораздо полезнее иметь возможность контролировать, какие конкретные блоки отладочного кода получают
включены. Попробуйте расширить синтаксис для блоков отладки, чтобы можно было идентифицировать каждый из них. В
содержимое переменной среды "DEBUG" затем можно использовать для управления тем, какие блоки получают
включены.
Как только вы сможете идентифицировать отдельные блоки, попробуйте разрешить им быть вложенными. Это не
тоже сложно.
Вот интересная идея, которая не связана с фильтром отладки. В настоящее время Perl
подпрограммы имеют довольно ограниченную поддержку списков формальных параметров. Вы можете указать
количество параметров и их тип, но вам все равно придется вручную вынимать их из
@_ массив самостоятельно. Напишите исходный фильтр, который позволит вам иметь список именованных параметров.
Такой фильтр повернет это:
sub MySub ($ first, $ second, @rest) {...}
в это:
sub MySub ($$ @) {
мой ($ первый) = сдвиг;
мой ($ секунда) = сдвиг;
мой (@rest) = @_;
...
}
Наконец, если вы чувствуете себя настоящим вызовом, попробуйте написать полноценный макрос Perl.
препроцессор как исходный фильтр. Воспользуйтесь полезными функциями препроцессора C и
любые другие известные вам макропроцессоры. Сложнее всего будет выбрать, сколько знаний
Синтаксис Perl, который должен иметь ваш фильтр.
ОГРАНИЧЕНИЯ
Исходные фильтры работают только на строковом уровне, поэтому их возможности сильно ограничены.
менять исходный код на лету. Он не может обнаруживать комментарии, строки в кавычках, heredocs, это
нет замены настоящему парсеру. Единственное стабильное использование исходных фильтров:
шифрование, сжатие или байтовый загрузчик, чтобы преобразовать двоичный код обратно в исходный код.
См., Например, ограничения в Switch, который использует исходные фильтры и, следовательно, не
работают внутри строки eval, наличие регулярных выражений со встроенными символами новой строки, которые
указаны с необработанными разделителями /.../ и не имеют модификатора // x неразличимы
из фрагментов кода, начинающихся с оператора деления /. В качестве обходного пути вы должны использовать
м /.../ или м? ...? для таких выкроек. Кроме того, наличие регулярных выражений, указанных с помощью raw? ...?
разделители могут вызвать загадочные ошибки. Обходной путь - использовать m? ...? вместо. Видеть
http://search.cpan.org/perldoc? Переключатель № ОГРАНИЧЕНИЯ
В настоящее время длина внутреннего буфера ограничена 32-битным.
ВЕЩИ К СМОТРИТЕ ВНЕ Для
Некоторые фильтры забивают ручку "DATA"
Некоторые исходные фильтры используют дескриптор «DATA» для чтения вызывающей программы. Когда используешь
эти исходные фильтры, вы не можете полагаться на этот дескриптор или ожидать какого-либо конкретного вида
поведения при работе с ним. Фильтры на основе Filter :: Util :: Call (и, следовательно,
Filter :: Simple) не изменяют дескриптор файла "DATA".
ТРЕБОВАНИЯ
Дистрибутив Source Filters доступен на CPAN, в
CPAN / модули / по модулям / фильтр
Начиная с Perl 5.8 Filter :: Util :: Call (основная часть исходных фильтров
дистрибутив) является частью стандартного дистрибутива Perl. Также включен более дружелюбный
интерфейс под названием Filter :: Simple, созданный Дэмианом Конвеем.
Используйте perlfilter онлайн с помощью сервисов onworks.net