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

Безопасность данных, которыми оперируют программисты в своих программах, всегда был предметом для дискуссий и размышлений. Первым и чуть ли не единственным инструментом при проверке и очистке переменных в 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().
Таким образом, используя эти функции, можно существенно сократить использование регулярных выражений в приложении, и, соответственно, увеличить скорость работы приложения.

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


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

2leep.com

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

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

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

  1. Автор: Костян, 23 января 2010 в 20:42

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

    сам пишешь эти классы?

  2. Автор: Алексей, 28 января 2010 в 11:19

    использует Firefox 3.5.7 Firefox 3.5.7 на Windows 7 Windows 7

    Совсем недавно тоже узнал о такой возможности в новой версии PHP 5.2. Немного упростит проверку данных. Только применяя это, нужно помнить, что это есть только в новых версиях PHP.

    • Автор: web-junior, 28 января 2010 в 17:59

      использует Firefox 3.5.7 Firefox 3.5.7 на Windows XP Windows XP

      Да конечно вы правы. Помнить нужно обязательно.
      Но с другой стороны, года через 2-3, никто уже и не будет помнить не только о версиях ниже 5.2, но и про саму 5.2 все забудут. Прогресс ведь не стоит на месте =)

  3. Автор: voksel, 7 февраля 2010 в 20:31

    использует Google Chrome 4.0.249.43 Google Chrome 4.0.249.43 на GNU/Linux GNU/Linux

    Спасибо за статейку.

    • Автор: web-junior, 7 февраля 2010 в 20:58

      использует Firefox 3.5.7 Firefox 3.5.7 на Windows XP Windows XP

      Пожалуйста. Заходите ещё. На сайте вы сможете найти ещё много интересных материалов :)

  4. Автор: Hows, 27 февраля 2010 в 13:16

    использует Firefox 3.5.7 Firefox 3.5.7 на Ubuntu 9.10 Ubuntu 9.10

    Полюбак, в самих же этих функциях используются регулярки

  5. Автор: Андрей, 19 апреля 2010 в 10:42

    использует Firefox 3.6.3 Firefox 3.6.3 на Windows 7 Windows 7

    Это сто процентов, что в них используются регулярки, но вот наверняка они отрабатываются быстрей чем рукописные.
    А у уважаемого автора есть наблюдения по этому поводу?

    • Автор: web-junior, 19 апреля 2010 в 11:07

      использует Firefox 3.5.8 Firefox 3.5.8 на Windows XP Windows XP

      Да, конечно, здесь используются регулярки. За подтверждением ходить далеко не нужно. Достаточно посмотреть в исходниках php:
      php-5.2.13/ext/filter/logical_filter.c

      494
      495
      496
      497
      498
      499
      500
      501
      502
      503
      504
      505
      506
      507
      508
      509
      510
      511
      512
      513
      514
      515
      516
      517
      518
      519
      
      ...
      void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
      {
      	/* From http://cvs.php.net/co.php/pear/HTML_QuickForm/QuickForm/Rule/Email.php?r=1.4 */
      	const char regexp[] = "/^((\\\"[^\\\"\\f\\n\\r\\t\\b]+\\\")|([A-Za-z0-9_][A-Za-z0-9_\\!\\#\\$\\%\\&\\'\\*\\+\\-\\~\\/\\=\\?\\^\\`\\|\\{\\}]*(\\.[A-Za-z0-9_\\!\\#\\$\\%\\&\\'\\*\\+\\-\\~\\/\\=\\?\\^\\`\\|\\{\\}]*)*))@((\\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9])(([A-Za-z0-9\\-])*([A-Za-z0-9]))?(\\.(?=[A-Za-z0-9\\-]))?)+[A-Za-z]+))$/D";
       
      	pcre       *re = NULL;
      	pcre_extra *pcre_extra = NULL;
      	int preg_options = 0;
      	int         ovector[150]; /* Needs to be a multiple of 3 */
      	int         matches;
       
       
      	re = pcre_get_compiled_regex((char *)regexp, &pcre_extra, &preg_options TSRMLS_CC);
      	if (!re) {
      		RETURN_VALIDATION_FAILED
      	}
      	matches = pcre_exec(re, NULL, Z_STRVAL_P(value), Z_STRLEN_P(value), 0, 0, ovector, 3);
       
      	/* 0 means that the vector is too small to hold all the captured substring offsets */
      	if (matches < 0) {
      		RETURN_VALIDATION_FAILED
      	}
       
      }
      ...

      Все дело в том, что на C эти регулярки работают намного быстрее, чем на PHP. В этом и есть основной выигрыш.

  6. Автор: Сергей, 26 августа 2010 в 01:02

    использует Google Chrome 5.0.375.127 Google Chrome 5.0.375.127 на Windows XP Windows XP

    Походу разработчики забыли, что в домене может встретиться знак “-”! Хотел использовать у себя для проверки правильно ввода URL, но вот незадача…

    • Автор: web-junior, 26 августа 2010 в 17:39

      использует Firefox 3.5.9 Firefox 3.5.9 на Windows XP Windows XP

      Ну почему-же? Вот мой домен, вполне верно обрабатывает. К примеру такой код:

      1
      2
      3
      4
      5
      
      ...
      $homepage = 'http://www.web-junior.net';
      var_dump(filter_var($homepage, 
      FILTER_VALIDATE_URL,FILTER_FLAG_SCHEME_REQUIRED));
      ...

      Выводит
      string(25) “http://www.web-junior.net”
      Т.е. обрабатывает верно.
      Если у вас какой-либо код работает не верно, то напишите его здесь и мы вместе попробуем разобраться в причинах его неработоспособности =)

  7. Автор: papay, 23 ноября 2010 в 11:56

    использует Firefox 3.6.12 Firefox 3.6.12 на Ubuntu 10.04 Ubuntu 10.04

    Спасибо. познавательная статья. Не знал про эти возможности PHP. Действительно может оказать существенную помощь при написании своих веб-приложений

    • Автор: web-junior, 24 ноября 2010 в 19:53

      использует Firefox 3.6.12 Firefox 3.6.12 на Windows 7 Windows 7

      Пожалуйста!
      Заходите еще, здесь вы сможете найти множество полезных материалов.

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="">

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