Долой регулярные выражения или новый способ проверки данных

Безопасность данных, которыми оперируют программисты в своих программах, всегда был предметом для дискуссий и размышлений. Первым и чуть ли не единственным инструментом при проверке и очистке переменных в PHP были регулярные выражения. Некоторые так увлекались этим делом, что составляли огромные выражения в соответствии со стандартами (видел одно рег. выражение для проверки email на 6 тыс. символов). В этой статье рассмотрим некоторую альтернативу регулярным выражениям для проверки и очистки данных.
В PHP, начиная с версии 5.2, присутствует группа функций для фильтрации данных. Это расширение не требует особой настройки и установки.

Фильтры
PHP разных версий может поддерживать различное количество фильтров. Для того, чтобы выяснить какие данные могут отфильтровать функции существует функция filter_list(). Для просмотра идентификатора конкретного фильтра, нужно использовать функцию filter_id().
Выведем список фильтров и их идентификаторы.
filter_list.php

<?php
	echo "<table><tr><td>Имя фильтра</td><td>Идентификатор</td>";
	$filters = filter_list();
	foreach($filters as $filter){
		echo "<tr><td>$filter</td><td>".filter_id($filter).
"</td></tr>";
	}
	echo "</table>";
	var_dump(filter_var('http://www.example.loc',filter_id('validate_url')));
?>
Имя фильтра Идентификатор
int 257
boolean 258
float 259
validate_regexp 272
validate_url 273
validate_email 274
validate_ip 275
string 513
stripped 513
encoded 514
special_chars 515
unsafe_raw 516
email 517
url 518
number_int 519
number_float 520
magic_quotes 521
callback 1024

Идентификатор фильтра используется для того, чтобы рассказать функциям фильтрации какой именно фильтр нужно применять.

Функции фильтрации
Для проверки и фильтрации одиночных переменных существует функция filter_var().

mixed filter_var ( mixed $variable [, int $filter [, 
mixed $options ]] )

Функция принимает переменную $variable, которую нужно отфильтровать, идентификатор фильтра $filter, а также некоторые опции фильтрации.
Идентификатор фильтра можно получить с помощью функции filter_id(), как было показано выше. Кроме того, это расширение предопределяет константы, в которых содержатся идентификаторы фильтров. Вот список констант.

Фильтры проверки данных

  • FILTER_VALIDATE_BOOLEAN – проверяет как булевое значение, соответствует фильтру boolean;
  • FILTER_VALIDATE_INT – проверяет как целое число, соответствует фильтру int. Опции фильтрации могут быть следующими:
    • min_range — минимальное число;
    • max_range — максимальное число;
    • FILTER_FLAG_ALLOW_OCTAL — разрешает восьмеричные числа;
    • FILTER_FLAG_ALLOW_HEX — разрешает шестнадцатеричные числа;
  • FILTER_VALIDATE_FLOAT – проверяет как число с плавающей точкой, соответствует фильтру float;
  • FILTER_VALIDATE_REGEXP – проверяет на соответствие регулярному выражению, соответствует фильтру validate_regexp;
  • FILTER_VALIDATE_URL – проверяет, является ли переменная правильным URL, соответствует фильтру validate_url. Опции фильтрации могут быть следующими:
    • FILTER_FLAG_SCHEME_REQUIRED — наличие протокола (http://example);
    • FILTER_FLAG_HOST_REQUIRED — наличие хоста (http://www.example.com);
    • FILTER_FLAG_PATH_REQUIRED — наличие пути (www.example.com/example1/test2/);
    • FILTER_FLAG_QUERY_REQUIRED — наличие строки запроса (example.php?name=Peter&age=37)
  • FILTER_VALIDATE_EMAIL – проверяет, является ли переменная правильным email, соответствует фильтру validate_email;
  • FILTER_VALIDATE_IP – проверяет, является ли переменная правильным IP, соответствует фильтру validate_ip. Опции фильтрации могут быть следующими:
    • FILTER_FLAG_IPV4 — значение должно быть IPv4 (255.255.255.255);
    • FILTER_FLAG_IPV6 — значение должно быть IPv6 ( 2001:0db8:85a3:08d3:1319:8a2e:0370:7334);
    • FILTER_FLAG_NO_PRIV_RANGE — значение должно соответствовать RFC, и не входить в приватные диапазоны (192.168.0.1, 10.0.0.1, …);
    • FILTER_FLAG_NO_RES_RANGE — значение не должно содержать зарезервированные IP адреса, этот флаг принимает и IPv4 и IPv6 (255.255.255.255);

Фильтры обработки данных

  • FILTER_SANITIZE_STRING – удаляет потенциально опасные данные из строки, соответствует фильтру string. Опции фильтрации могут быть следующими:
    • FILTER_FLAG_NO_ENCODE_QUOTES — не кодировать кавычки;
    • FILTER_FLAG_STRIP_LOW — удалить все ASCII-символы с кодом меньше 32;
    • FILTER_FLAG_STRIP_HIGH — удалить ASCII-символы с кодом больше 127;
    • FILTER_FLAG_ENCODE_LOW — кодировать все ASCII-символы с кодом меньше 32;
    • FILTER_FLAG_ENCODE_HIGH — кодировать все ASCII-символы с кодом больше 127;
    • FILTER_FLAG_ENCODE_AMP — кодировать символ & в &amp;
  • FILTER_SANITIZE_STRIPPED – то же что и FILTER_SANITIZE_STRING;
  • FILTER_SANITIZE_ENCODED – удаляет или кодирует нежелательные символы (похоже на urlencode()), соответствует фильтру encoded. Опции фильтрации могут быть следующими:
    • FILTER_FLAG_STRIP_LOW — удалить все ASCII-символы с кодом меньше 32
    • FILTER_FLAG_STRIP_HIGH — удалить все ASCII-символы с кодом больше 32
    • FILTER_FLAG_ENCODE_LOW — кодировать все ASCII-символы с кодом меньше 32
    • FILTER_FLAG_ENCODE_HIGH — кодировать все ASCII-символы с кодом больше 32
  • FILTER_SANITIZE_SPECIAL_CHARS – преобразовывает спец.символы в HTML-сущности, соответствует фильтру special_chars. Опции фильтрации могут быть следующими:
    • FILTER_FLAG_STRIP_LOW — удалить все ASCII-символы с кодом меньше 32
    • FILTER_FLAG_STRIP_HIGH — удалить все ASCII-символы с кодом больше 32
    • FILTER_FLAG_ENCODE_HIGH — кодировать все ASCII-символы с кодом больше 32
  • FILTER_SANITIZE_EMAIL – удаляет все символы, запрещенные в email, соответствует фильтру email;
  • FILTER_SANITIZE_URL – удаляет все символы, запрещенные в URL, соответствует фильтру url;
  • FILTER_SANITIZE_NUMBER_INT – удаляет все символы, кроме целых чисел, соответствует фильтру number_int;
  • FILTER_SANITIZE_NUMBER_FLOAT – удаляет все символы, кроме чисел с плавающей точкой, соответствует фильтру number_float;
  • FILTER_SANITIZE_MAGIC_QUOTES – добавляет слэши к спец символам, соответствует фильтру magic_quotes.

Напишем небольшой тестовый код, который продемонстрирует работу фильтров.
filter_var.php

<?php
	//имя
	$name = 'web-junior';
	//email
	$email = 'test@email.com';
	//URL
	$homepage='www.web-junior.net';
	//возраст - целое число
	$age=30;
	//сначала отфильтруем имя
	//все спец-символы закодируем
	var_dump(filter_var($name, FILTER_SANITIZE_STRING, 
FILTER_FLAG_ENCODE_LOW));
	echo '<br/>';
	//проверим email. 
	//Проверка, если честно не очень.
	var_dump(filter_var($email, FILTER_VALIDATE_EMAIL));
	echo '<br/>';
	//проверим URL.
	//Должен вернуть false, поскольку протокол не указан
	var_dump(filter_var($homepage, 
FILTER_VALIDATE_URL,FILTER_FLAG_SCHEME_REQUIRED));
	echo '<br/>';
	//проверяем возраст:
	//должно быть от 18 до 50
	var_dump(filter_var($age, 
		FILTER_VALIDATE_INT, 
		array('options'=>array(
			'min_range'=>18,
			'max_range'=>50)
			)
		)
	);
 
?>

Выведет:

string(10) “web-junior”
string(14) “test@email.com”
bool(false)
int(30)

Следующая функция для фильтрации данных называется filter_var_array()

mixed filter_var_array ( array $data [, mixed $definition ] )

По сути выполняет те же операции что и filter_var(), но только делает это с данными в массиве. Первый параметр – это массив данных, второй – это массив с указаниями как фильтровать данные. Вот пример.
<strong>filter_var_array.php</strong>

<?php
//данные
$data = array(
    'product_id'    => 'libgd<script>',
    'component'     => '10',
    'versions'      => '2.0.33',
    'testscalar'    => array('2', '23', '10', '12'),
    'testarray'     => '2',
);
//указания как фильтровать
$args = array(
    'product_id'   => FILTER_SANITIZE_ENCODED,
    'component'    => array('filter'    => FILTER_VALIDATE_INT,
                            'flags'     => FILTER_FORCE_ARRAY, 
                            'options'   => array('min_range' => 1, 'max_range' => 10)
                           ),
    'versions'     => FILTER_SANITIZE_ENCODED,
    'doesnotexist' => FILTER_VALIDATE_INT,
    'testscalar'   => array(
                            'filter' => FILTER_VALIDATE_INT,
                            'flags'  => FILTER_REQUIRE_SCALAR,
                           ),
    'testarray'    => array(
                            'filter' => FILTER_VALIDATE_INT,
                            'flags'  => FILTER_FORCE_ARRAY,
                           )
 
);
 
//применяем фильтры
$myinputs = filter_var_array($data, $args);
//выводим
var_dump($myinputs);
echo "\n";
?>

Этот код выведет

array(6) {
["product_id"]=>
string(17) “libgd%3Cscript%3E”
["component"]=>
array(1) {
[0]=>
int(10)
}
["versions"]=>
string(6) “2.0.33″
["doesnotexist"]=>
NULL
["testscalar"]=>
bool(false)
["testarray"]=>
array(1) {
[0]=>
int(2)
}
}

Судя по всему, разработчики этих функций во время разработки задались вопросом: «Какие данные чаще всего приходится проверять?» И пришли к единственно верному логическому ответу: «Те, которые приходят от пользователя». Придя к такому выводу, они создали функцию, которая проверяет данные в массивах $_GET, $_POST, $_COOKIE, $_SERVER и $_ENV.

mixed filter_input ( int $type , string $variable_name [, 
int $filter [, mixed $options ]] )

$type может быть одной из констант INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, INPUT_ENV, которая обозначает соответствующий входящий массив. $variable_name – это имя переменной в массиве, а $filter и $options аналогичны filter_var().
Бывает достаточно удобно применить фильтр сразу к данным, которые пришли извне.
Сделаем на скорую руку форму.
form.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" lang="en" 
xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Form</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body>
<form action="filter_input.php" method="post">
<label for="name">Имя</label><input id="name" /><br/>
<label for="email">Email</label><input id="email" /><br/>
<label for="homepage">Домашняя страничка</label><input type="text" /><br/>
<label for="age">Возраст</label><input id="age" /><br/>
<input type="submit" value="Отправить" />
</form>
</body>
</html>

Вот форма.

Форма получилась самой простой. Но нам больше и не нужно.

Теперь получим и выведем все что получили, обрабатывая результат функцией filter_input()
filter_input.php

<?php
	//фильтруем имя
	//все непечатаемые символы будут закодированы
	$name = filter_input(INPUT_POST,'name', 
FILTER_SANITIZE_STRING,FILTER_FLAG_ENCODE_LOW);
	//проверяем email
	$email = filter_input(INPUT_POST, 
'email',FILTER_VALIDATE_EMAIL);
	//URL должен содержать протокол
	$homepage = filter_input(INPUT_POST, 'homepage', 
FILTER_VALIDATE_URL,FILTER_FLAG_SCHEME_REQUIRED);
	//возраст
	//вход разрешен с 18 и до 50
	$age = filter_input(INPUT_POST, 'age',FILTER_VALIDATE_INT, 
array('options'=>array('min_range'=>18,'max_range'=>50)));
 
	//посмотрим что получили
	echo "Ваше имя ".($name!=false?$name:
'введено неверно')."<br/>
	Ваш email ".($email!=false?$email:'введен неверно')."<br/>
	Ваша домашняя страничка ".
($homepage!=false?$homepage:'введена неверно')."<br/>
	Ваш возраст ".($age!==false?$age:'введен неверно')."
	";
?>

В результате отправки данных с формы мы получим что-то вроде

Все данные отфильтрованы успешно

Следующая функции для фильтрации

mixed filter_input_array ( int $type [, mixed $definition ] )

Функция filter_input_array() своей функциональностью очень похожа на функции filter_input() и filter_var_array(). Эта функция фильтрует несколько переменных из входящих данных. Приведенный выше пример можно переписать так.
filter_input_array.php

<?php
	$defs = array(
		'name'=>array(
			'filter'=>FILTER_SANITIZE_STRING,
			'flags'=>FILTER_FLAG_ENCODE_LOW
		),
		'email'=>FILTER_VALIDATE_EMAIL,
		'homepage'=>array(
			'filter'=>FILTER_VALIDATE_URL,
			'flags'=>FILTER_FLAG_SCHEME_REQUIRED
		),
		'age'=>array(
			'filter'=>FILTER_VALIDATE_INT,
			'options'=>array('min_range'=>18,'max_range'=>50)
		)
	);
 
	$res = filter_input_array(INPUT_POST,$defs);
	//посмотрим что получили
	echo "Ваше имя ".($res['name']!=false?$res['name']:
'введено неверно')."<br/>
	Ваш email ".($res['email']!=false?$res['email']:
'введен неверно')."<br/>
	Ваша домашняя страничка ".($res['homepage']!=false?$res['homepage']:
'введена неверно')."<br/>
	Ваш возраст ".($res['age']!==false?$res['age']:
'введен неверно')."
	";
?>

Результат работы этого скрипта будет таким, как и filter_input.php.
Последняя функция в этом расширении называется filter_has_var().

bool filter_has_var ( int $type , string $variable_name )

Эта функция проверяет существует ли переменная $variable_name во входящих данных типа $type. Тип может быть таким же как и в функции filter_input().
Таким образом, используя эти функции, можно существенно сократить использование регулярных выражений в приложении, и, соответственно, увеличить скорость работы приложения.

Популярность: 14%


Интересное из других блогов:

2leep.com

И не забывайте комментировать статью.

Добавляйся в группу во вконтакте, чтобы самым первым узнавать все новости сайта

Отзывов: 14 на «Долой регулярные выражения или новый способ проверки данных»

  1. Автор: Griffs, 8 декабря 2010 в 23:43

    использует Opera 10.10 Opera 10.10 на Windows XP Windows XP

    Спасибо автору, очень познавательная статья.

    Нашел только один недочет в функции:

    filter_input(INPUT_POST, ‘email’,FILTER_VALIDATE_EMAIL);

    При вводе такой конструкции в поле мейла: email@email – не фильтрует, а возвращает как корректный.

  2. Автор: Alexander, 22 июля 2011 в 22:57

    использует Opera 10.63 Opera 10.63 на Windows XP Windows XP

    Спасибо. Правда говоря – ну и констант мне придётся учить. Хорошо что есть много знакомых названий.

RSS-лента комментариев. Адрес для трекбека

Ваш отзыв

Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Нажимая на кнопку "Добавить" вы принимаете правила комментирования