Структура кода правила состоит из следующих элементов:
- необязательный блок определения переменных;
- предикаты, объединенные в булеву формулу при помощи функций AND, OR, NOT и скобок.
Пример общей структуры правила:
with @var1=value_1, ..., @varn=value_n if startVar_1 . f_11() . f_12() ... . f_1i1() compare_operator compare_values1 and ( startVar_2 . f_21() . f_22() ... . f2i2() . compare_function(compare_values2) or ... )
Результат правила определяется в следующем порядке: сначала выполняется вычисление всех отдельных предикатов (значение каждого будет ИСТИНА или ЛОЖЬ), затем выполняется вычисление общего логического выражения. Полученное значение будет конечным результатом правила.
Каждый предикат состоит из трех частей:
- начальной переменной, которая обязательно должна быть контекстной (возможно через переопределения);
- набора преобразований над входными данными (HTTP-запросами, HTTP-ответами);
- оператора, выполняющего операцию над текущим результатом преобразований и аргументами.
Пример структуры предиката:
startVariable . function_1([args]) . function_2([args]) compare_operator compare_values
Регистр ключевых слов и контекстных переменных не важен, а пользовательские переменные и пользовательские параметры необходимо вводить с учетом регистра.
Переменные
Переменные в правилах могут быть трех типов:
- пользовательские. Задаются в коде правила пользователем и им же инициализируются. Имеют префикс
@
; - параметры правила. Задаются в PT AF при создании или изменении правила. Имеют префикс
@@
; - контекстные. Переменные, формируемые PT AF на основании HTTP-запроса или HTTP-ответа и включающие какие-либо их поля.
Контекстные переменные бывают векторные (например, REQUEST_GET, REQUEST_POST) и скалярные (например, CLIENT_IP, RESPONSE_STATUS_CODE).
Пример использования переменных:
with @cookies = REQUEST_COOKIES, @path = "/test.php", @code = 403 if @cookies.extract(keys).any() IN @@cookie_names AND REQUEST_PATH == @path AND RESPONSE_STATUS_CODE == @code
Типы данных
Типы данных языка пользовательских правил:
- Int. Целочисленный.
Литерал представляет собой последовательность цифр (допустимы незначащие начальные нули). Например:
0001 123456 - Ip. IP-адреса.
Литералы построены согласно нотации CIDR. Могут указываться без маски подсети. Например:
192.168.1.1 1.0.0.0/24 - List. Список.
Литералы используют квадратные скобки, в которых через запятую перечисляются элементы списка. Элементы списка должны быть одного типа. Например:
[1, 2, 3, 4, 5] ["foo", "bar", "zoo"] - Regex. Регулярные выражения (используется PCRE-синтаксис, поддерживаемый движком HyperScan).
Литерал строится так же, как и литерал для строки, но в начале литерала добавляется тильда. Например:
~"[abc][0-9]" ~"\\d+" ~"Lua pattern can be just text" - String. Строковый.
Литерал представляет собой последовательность символов в двойных кавычках. Внутри строки не может быть двойной кавычки. Символ экранирования, обратная косая черта, также не может быть включен отдельно, он должен что-то экранировать. Например:
"" "hello world" "This literal includes backslash: \\ and double quotes: \""
При инициализации предикат получает значение и тип данных начальной контекстной переменной. Во всех случаях, кроме упомянутых ниже исключений, это строковый тип или словарь строк — в зависимости от того, является ли исходная контекстная переменная скалярной или векторной. Далее, при наличии преобразований в предикате, его значение и тип данных последовательно меняются на значение и тип данных результата очередной функции преобразования.
Преобразования
Преобразования используются для формирования требуемых для анализа элементов входных данных.
Виды преобразований:
- All. Модифицирует поведение последующего оператора так, что истинным этот оператор будет, только если оно истинно для каждого элемента текущего значения предиката. Такое преобразование имеет силу, только если применяется последним в списке, то есть непосредственно после него следует оператор. Может применяться только к списочным типам.
- Any. Модифицирует поведение последующего оператора так, что истинным этот оператор будет, только если оно истинно хотя бы для одного элемента текущего значения предиката. Такое преобразование имеет силу, если применяется последним в списке, то есть непосредственно после него следует оператор. Может применяться только к списочным типам.
- Extract. Извлечение данных текущего значения предиката на основании аргумента. Нахождение «проекций» составных типов: по ключам (KEYS) или значениям (VALUES).
- Length. Подсчет количества элементов текущего значения предиката. Для скалярного контекста результатом будет длина строки, для векторного — количество элементов списка.
- Map. Конвертация. Функция применяет преобразования к каждому элементу в текущем контексте предиката. Может применяться к векторному и скалярному контексту вычисления. Необходимые преобразования явно указываются в качестве аргумента и применяются последовательно слева направо в порядке перечисления. При этом выходной тип предыдущего преобразования должен быть совместим с входным типом следующего. Список доступных преобразований:
- BASE64_DECODE — преобразование base64_encoded_string в decoded_string;
- HEX_DECODE — преобразование hex_encoded_string в decoded_string;
- HTML_ENTITY_DECODE — преобразование html_entity_encoded_string в decoded_string;
- LENGTH — преобразование строки в длину строки;
- LOWERCASE — преобразование в нижний регистр;
- UPPERCASE — преобразование в верхний регистр;
- URL_DECODE — преобразование url_encoded_string в decoded_string.
- Select. Фильтрация данных текущего значения предиката на основании аргумента. Аргументом может быть строка (фильтрация по прямому совпадению) или регулярное выражение (фильтрация по регулярному выражению).
Примеры правил с использованием преобразований:
if REQUEST_GET.extract(values).map(length).all() between 100 and 200 if REQUEST_GET.extract(values).map(base64_decode, hex_decode, uppercase).any() == "some value" if REQUEST_HEADERS.select("Content-Type").extract(values).map(lowercase).any() matches "application/json"
Операторы
Операторы используются для проверки текущего значения предиката на соответствие критерию, определяемому смыслом оператора и его аргументами. Синтаксис аргументов для разных операторов может отличаться. В качестве значений аргументов могут использоваться литерал, пользовательская переменная или параметр совместимого с текущим контекстом вычисления типа. Результатом работы оператора является логическое значение ИСТИНА или ЛОЖЬ. Используется два типа операторов: инфиксные и постфиксные.
К операторам с инфиксной записью относятся:
- Реляционные операторы. Имеют буквенный и символьный варианты:
- EQ (==) — равно;
- NE (!=) — не равно;
- GE (>) — больше, чем;
- LE (<) — меньше, чем;
- GT (>=) — больше или равно;
- LE (<=) — меньше или равно.
Примеры использования реляционных операторов:
if REQUEST_GET . length() > 3 if REQUEST_HEADERS . extract(keys) . map(lowercase) . any() eq "user-agent" - BETWEEN. Проверка вхождения в диапазон. Включает нижнюю и верхнюю границы. Аргументы: значения для нижней и верхней границы, разделенные словом
and
. Пример:if REQUEST_HEADERS . length() between 1 and 10
- CONTAINS. Проверка того, является ли список аргумента подмножеством списка предиката. Возвращает ИСТИНА, если все элементы списка аргумента присутствуют в списке предиката. Применяется только к списочному предикату. Аргумент: список в виде литерала, пользовательской переменной или параметра правила. Каждое значение списка должно быть того же типа, что и текущий контекст вычисления. Пример:
if REQUEST_POST . extract(keys) contains ["password","user"]
- IN. Проверка наличия в множестве. Аргументы: список в виде литерала, пользовательской переменной или параметра правила. Каждое значение списка должно быть того же типа, что и текущий контекст вычисления. Пример:
with @range = [303, 422, 501] if RESPONSE_STATUS_CODE in @range - MATCHES. Проверка на соответствие регулярному выражению или подстроке. Аргумент: регулярное выражение или строка. Пример:
IF REQUEST_HEADERS . extract(keys) . any() matches ~"^X-Test\\d+$"
К операторам с постфиксной записью относится EXISTS. Проверка существования значений. Применяется только для векторных типов. Аргументы отсутствуют. Пример:
if REQUEST_GET . exists()