Это продолжение статьи об эмуляции вложенных if в nginx с помощью регулярных выражений. На этот раз будем решать другую задачу: проверим, находится ли значение какого-нибудь объекта, например куки a, в некотором заданном списке. Пусть в этом списке, назовем его a_list, находятся значения, разделенные точкой с запятой. При равенстве куки a одному из значений в списке будем выполнять специальное действие, для простоты — выводить строку PASSED, в противном случае — выводить строку FAILED.
Вот конфигурация nginx.
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
set $a_list " foo; bar ; 1qwerty;a";
location / {
set $check_a $a_list::$cookie_a;
if ($check_a ~* "(?:[^;]+\s*;\s*)*(?<=;|^)\s*([^;]+)\s*(?=;|::).*::\1$") {
echo "PASSED";
break;
}
echo "FAILED";
}
}
}
Я намеренно расставил разное количество пробелов вокруг точек с запятыми в списке a_list, чтобы показать, что это не будет являться проблемой при правильно составленном регулярном выражении в условии if. Как и в предыдущей статье, здесь создается проверочная переменная check_a, состоящая из двух частей — списка a_list и значения куки a, разделенных двумя двоеточиями. Внутри регулярного выражения в условии if два двоеточия соответствуют этому разделению двух переменных. В правой части выражения, которая соответствует значению куки a, находится обратная ссылка \1
на значение, выделенное круглыми скобками в левой части — в них мы ожидаем одно из значений из списка a_list. Таким образом, если значение куки a совпадет с одним из значений в списке a_list, то переменная check_a будет соответствовать этому регулярному выражению и условие if окажется верным.
Самое сложное здесь — это составить выражение для левой части регулярного выражения в условии if. Его центральная часть — атом ([^;]+)
, который будет соответствовать обратной ссылке \1
из правой части. Этот атом может заканчиваться некоторым количеством пробельных символов (\s*
) и точкой с запятой, либо двумя двоеточиями ((?=;|::)
), если значение справа окажется равным последнему элементу из списка a_list. Перед центральным атомом могут находиться другие элементы, разделенные точкой с запятой ((?:[^;]+\s*;\s*)*
), которые нас не интересуют, а также некоторое количество пробельных символов (\s*
). Важно проверить, что перед центральным атомом в левой части, включая возможные пробельные символы вначале, стоит точка с запятой, либо это начало строки ((?<=;|^)
), иначе хвосты элементов из списка a_list, такие как oo для foo или даже y для 1qwerty приведут к срабатыванию всего регулярного выражения.
Давайте проверим конфигурацию.
curl -b 'a=foo' 'http://localhost:80/'
PASSED
curl -b 'a=oo' 'http://localhost:80/'
FAILED
curl -b 'a=foo1' 'http://localhost:80/'
FAILED
curl -b 'a=a' 'http://localhost:80/'
PASSED
curl -b 'a=' 'http://localhost:80/'
FAILED
curl -b 'a=qwert' 'http://localhost:80/'
FAILED
curl -b 'a=1qwerty' 'http://localhost:80/'
PASSED
Работает! А теперь усложним задачу. Будем сравнивать не на точное соответствие значения переменной справа, а на присутствие в ней слова из списка a_list. Это еще не всё. Пусть при этом значение некоторой другой переменной (например, cookie_b) будет равно SUCCESS — опять эмуляция вложенных if! Где это можно применить? Подставьте вместо cookie_b имя ssl_client_verify, а вместо cookie_a — имя ssl_client_i_dn. Получаем проверку клиентского SSL сертификата с дополнительной проверкой того, что SSL issuer входит в заготовленный нами список a_list (теперь мы можем назвать этот список более осмысленно, например valid_ssl_issuers).
Итак, добавим в нашу конфигурацию новый локейшн.
location /2 {
set $check_a $cookie_b::$a_list::$cookie_a;
if ($check_a ~* "^SUCCESS::(?:[^;]+\s*;\s*)*(?<=;|::)\s*([^;]+)\s*(?=;|::).*::.*\b\1(?:\b|$)") {
echo "PASSED";
break;
}
echo "FAILED";
}
Что изменилось? Переменная check_a теперь состоит из трех частей — первой частью является значение куки b, которое мы будем проверять на соответствие значению SUCCESS. Оставшиеся две части, как и раньше — элементы списка a_list и значение куки a. Соответственно изменилось регулярное выражение в условии if. Теперь оно начинается с SUCCESS::
. Вторая часть, за исключением look-behind атома (?<=;|::)
, который теперь проверяет, что центральный атом центральной части выражения начинается с точки с запятой либо с двух двоеточий, возможно дополненных пробельными символами, не изменилась. Наконец, последняя часть, которая соответствует значению куки a, претерпела небольшие изменения — в ней проверяется, что где-либо внутри значения куки a находится какое-либо слово из списка a_list (.*\b\1(?:\b|$)
).
Проверяем.
curl -b 'b=SUCCESS; a=foo' 'http://localhost:80/2'
PASSED
curl -b 'b=SUCCES; a=foo' 'http://localhost:80/2'
FAILED
curl -b 'b=SUCCESS; a=rty' 'http://localhost:80/2'
FAILED
curl -b 'b=SUCCESS; a=1qwerty' 'http://localhost:80/2'
PASSED
curl -b 'b=SUCCESS; a=--1qwerty' 'http://localhost:80/2'
PASSED
curl -b 'b=SUCCESS; a=7-1qwerty:7' 'http://localhost:80/2'
PASSED
Все правильно. Напомню, что символы -
и :
являются границами слова, поэтому два последних случая удовлетворяют нашему регулярному выражению.