четверг, 24 апреля 2014 г.

nginx: эмуляция сложных операций в условии if с помощью регулярных выражений

Это продолжение статьи об эмуляции вложенных 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
Все правильно. Напомню, что символы - и : являются границами слова, поэтому два последних случая удовлетворяют нашему регулярному выражению.

Комментариев нет:

Отправить комментарий