Струк­ту­ра и син­так­сис язы­ка поль­зо­ва­тель­ских пра­вил

Струк­ту­ра кода пра­ви­ла со­сто­ит из сле­ду­ю­щих эле­мен­тов:

  • необя­за­тель­ный блок опре­де­ле­ния пе­ре­мен­ных;
  • пре­ди­ка­ты, объ­еди­нен­ные в бу­ле­ву фор­му­лу при по­мо­щи функ­ций 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: \""

При ини­ци­а­ли­за­ции пре­ди­кат по­лу­ча­ет зна­че­ние и тип дан­ных на­чаль­ной кон­текст­ной пе­ре­мен­ной. Во всех слу­ча­ях, кро­ме упо­мя­ну­тых ниже ис­клю­че­ний, это стро­ко­вый тип или сло­варь строк — в за­ви­си­мо­сти от того, яв­ля­ет­ся ли ис­ход­ная кон­текст­ная пе­ре­мен­ная ска­ляр­ной или век­тор­ной. Да­лее, при на­ли­чии пре­об­ра­зо­ва­ний в пре­ди­ка­те, его зна­че­ние и тип дан­ных по­сле­до­ва­тель­но ме­ня­ют­ся на зна­че­ние и тип дан­ных ре­зуль­та­та оче­ред­ной функ­ции пре­об­ра­зо­ва­ния.

К ис­клю­че­ни­ям от­но­сят­ся па­ра­мет­ры CLIENT_IP с ти­пом IP и RESPONSE_STATUS_CODE с ти­пом int.

Пре­об­ра­зо­ва­ния

Пре­об­ра­зо­ва­ния ис­поль­зу­ют­ся для фор­ми­ро­ва­ния тре­бу­е­мых для ана­ли­за эле­мен­тов вход­ных дан­ных.

Виды пре­об­ра­зо­ва­ний:

  • 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()