WordPress как на ладони
wordpress jino

Валидация данных в WordPress

Небезопасные данные могут прийти из разных источников: пользователи, другие сайты, ваша база данных и т.д. Такие данные должны очищаться при выводе на экран или в SQL запросе, или других опасных случаях... Новички часто недооценивают эту «не обязательную» процедуру - ведь все же и так работает...

В этой статье я опишу все возможные способы очистки и проверки данных в WordPress. WordPress предлагает много функций, чтобы грамотно проверить и очистить данные. Многие из функций описаны на этом сайте, но общего руководства по их использованию тут нет. Точнее, не было, до этой статьи.

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

Принципы проверки и очистки данных

1. Не доверяйте никаким данным

Любые данные вводимые пользователем потенциально опасны, даже если они выводятся в админке, только для администратора.

Также относитесь к данным получаемым из базы данных как к потенциально опасным. Даже если при добавлении данных вы произвели их очистку. Дело в том, что данные могут использоваться в разных контекстах. Например, заголовки могут содержать кавычки и вызовут ошибки если использовать их в атрибуте html тэга...

2. Проверяйте на входе, очищайте на выходе

Выводимые данные нужно очищать как можно позднее, в идеале прямо перед выводом на экран. Логика думаю понятна: не думаем об очистке вообще, кроме случаев вывода данных на экран...

Принимаемые данные наоборот: нужно очищать как можно раньше, в идеале прямо на входе. Но, при этом, их также нужно чистить перед записью в базу данных. Дело в том, что очистка для SQL запроса может отличаться от начальной очистки, например при входе мы удалил все теги и думаем что данные очищены, но такая очистка не обезопасит от SQL инъекций, т.е. это разные контексты очистки...

3. Доверяйте WordPress

Все «главные» функции WordPress (обертки, функции высшего порядка) проверяют и очищают данные. Поэтому при использовании таких функций не нужно думать о проверке или очистке передаваемых данных. Вот пример, где WordPress сам подготовит передаваемые данные для использования в запросе:

$posts = get_posts( array(
	's' => $_GET['s']
) );

К таким функциям можно отнести все функции, которые что-то выводят на экран или что-то добавляют в базу данных, но при этом вы не составляете SQL запрос самостоятельно, например это: $wpdb->update(), $wpdb->insert(), wp_update_post() и т.д. Вывод: the_title(), the_content() и т.д.

4. Числа хранящиеся в базе данных всегда безопасны

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

Числовые данные нужно всегда проверять/очищать на входе, простыми intval() или (int) $int, или floatval().

5. Лучше лишний раз очистить

Если разобраться, то очистка данных нужна не всегда. Но при разработке и так приходится за многим следить, и отвлекаться теряя драгоценное время и внимание на очистку порой нерационально. Поэтому если сомневаетесь, лучше лишний раз очистить в нужном контексте перед выводом/вводом, чем потом ловить баги, или еще хуже — иметь дыры в коде...

-

Есть несколько подходов к тому, как нужно очищать/проверять данные. Каждый их них подойдет для разных случаев.

Белый список

Проверять данные из имеющегося белого списка и допускать их, если они там есть.

При сравнении данных со списком, важно чтобы данные проверялись с учетом типа (=== или !==). В противном случае, иногда хакер может пройти проверку. Это правило касается случаев, когда проверяются числа (см. пример).

Операторы сравнения

$val = '1 опасная строка';  // станет 1 если делать проверку без учета типа данных

if ( 1 === $val ) {
	// если бы проверка была с == то она была бы пройдена
	echo 'Допустимое значение';
}
else {
	wp_die( 'НЕ допустимое значение' );
}

in_array()

$val = '1 опасная строка';  // станет 1 если делать проверку без учета типа данных

$safe_values = array( 1, 5, 7 ); // белый список

if ( in_array( $val, $safe_values, true ) ) {
	// `true` указывает что нужно учитывать тип данных - точная проверка
	echo 'Допустимое значение';
}
else {
	wp_die( 'НЕ допустимое значение' );
}

switch()

$val = '1 опасная строка';  // станет 1 если делать проверку без учета типа данных

switch ( true ) {
	case 1 === $val:  // нужно отдельно проверять с учетом типа, а не надеется на switch
		echo 'Допустимое значение';
		break;

	default:
		wp_die( 'НЕ допустимое значение' );
}

Черный список

Не пропускать данные, если они есть в черном списке. Чаще всего это лучшая проверка, если такую проверку вообще можно сделать.

Определение формата

Нужно убедиться, что данные пришли в нужном формате, в противном случае их не допускать.

// Пройдет если $data состоит только из букв и цифр: 'AbCd1zyZ9' - да, 'foo!#$bar' - нет
if ( ! ctype_alnum( $data ) ) {
	wp_die( 'Неверный формат' );
}

// проверка по символам: могут быть только 0-9.-
if ( preg_match( '/[^0-9.-]/', $data ) ) {
	wp_die( 'Неверный формат' );
}

Исправление формата

Когда независимо от обстоятельств нужно принять данные, то их можно очистить от опасных символов.

// когда нужно получит только число
$trusted_integer = (int) $untrusted_integer;

// когда нужно получит только строчные буквы
$trusted_alpha = preg_replace( '/[^a-z]/i', "", $untrusted_alpha );

// когда нужно получит стандартный ярлык (slug)
$trusted_slug = sanitize_title( $untrusted_slug );

Функции очистки

Ниже перечислены функции очистки данных, которые можно использовать при получении и записи данных в базу данных или при выводе данных на экран.

Многие функции очистки вводимых данных подходят и для очистки принимаемых данных.

Числа

intval( $int ) или (int) $int
Превращает $int в целое число. Это функции PHP.
absint( $int )
Превращает $int в целое положительное число. Т.е. если передать -5 вернет 5.
floatval( $int )
Превращает $int дробное число (число с плавающей точкой). Это функция PHP.

Массивы

array_map( 'absint', $array )

Превращает все значения $array в неотрицательные числа. array_map() — это PHP функция.

'absint' — это функция absint(), которая применяется к значением массива $array. Замените 'absint' на любое другое название функции и все значения массива будут обработаны этой функцией.

map_deep( $value, $callback ) (с версии 4.4)
Применяет указанную функцию к значениям переданного массива или свойствам объекта. Рекурсивная функция.

Строки (вывод на экран)

esc_html( $text )
Заменяет спецсимволы на HTML сущности. Заменяются следующие символы: &, <, >, ", '.
esc_html__( $text, $domain )
Тоже самое что esc_html(), только еще переводит текст. Это объединение функций esc_html() и __().
esc_html_e( $text, $domain )
Тоже самое что esc_html__(), только сразу выводит результат на экран, а не возвращает его для обработки. Объединение функций esc_html() и _e().
esc_textarea( $text )
Очищает текст/строку для использования в html теге textarea.
esc_attr( $text )
Используется для вывода любого текста в атрибуте тега. Кодирует знаки < > & " ' (меньше, больше чем, амперсанд, двойные и одинарные кавычки). Аналог esc_html().
esc_attr__( $text, $domain )
Переводит с использованием функции esc_attr(). Объединение функций esc_attr() и __().
esc_attr_e( $text, $domain )
Тоже что и esc_attr__(), только результат сразу выводиться на экран. Объединение функций esc_attr() и _e().

Строки (принимаемые данные)

В WordPress есть ряд функций с помощью которых быстро можно очистить специальные строки, те, которые должны содержать какие-то стандартные данные:

sanitize_text_field( $text )
Очищает строку передаваемую из поля input (обычно при сохранении в базу данных) или при получении из БД. Удаляет почти все оставляя только текст: без HTML тегов, переносов строк и т.д.
sanitize_textarea_field( $text )
Очищает строку передаваемую из поля textarea (при сохранении в базу данных) или при получении из БД. Удаляет все HTML символы, табуляции, HTML сущности и т.д. Оставляет чистый текст. С версии WP 4.7.
sanitize_html_class( $text )
Подготавливает текст для использования его в html атрибуте class: удаляет все неподходящие символы.
sanitize_title( $title )
Используется для создания ярлыков (slug) записей/рубрик.
sanitize_user( $username, $strict )
Очищает имя пользователя (логин, username), удаляя небезопасные символы.
$strict = true — значит в именах будут доступны только: [a-zA-Z0-9 _*.-].
sanitize_file_name( $filename )
Очищает название файла, заменяя пробелы на '_' и удаляя недопустимые символы.
sanitize_key( $key )
Очищает строку, чтобы использовать её как ключ. Ключи используются как разные внутренние ID. Оставит только: a-z0-9_-.
sanitize_mime_type( $mime_type )
Очищает строку для использования её как MIME тип. Удаляет все кроме -+*.a-zA-Z0-9/.
sanitize_title_with_dashes( $title )
Очищает заголовок, заменяя пробелы на (-).
sanitize_term_field( $field, $value, $term_id, $taxonomy, $context )
Очищает значение термина (рубрики) для использования в тексте.
sanitize_option( $option, $value )
Очищает значения различных опций, в зависимости от типа опции.

Строки (HTML)

wp_kses( $string, $allowed_html )

Чистит строку, оставляя в ней только указанные/допустимые HTML теги, их атрибуты и значения атрибутов. Следует использовать при выводе текста где могут быть небезопасные HTML теги.

Для удобного использования, у wp_kses() есть обертки. Например, чтобы не передавать массив допустимых тегов, а использовать базовый с минимальным набором допустимых html тегов, можно использовать:

wp_kses_post( $text ) — удалить недопустимые для записи теги, учитывая права текущего пользователя.

wp_kses_data( $text ) — если нужно ограничить допустимые теги до минимума, как это делается в комментариях. Ожидает не экранированный текст.

wp_filter_kses( $text ) — тоже что и wp_kses_data(), только учитывает экранирование в переданном тексте.

Фильтры wp_kses() медленные и съедают много ресурсов, поэтому не стоит использовать их каждый раз при выводе данных, лучше очищать ими входящие данные, например перед записью текста в базу данных. Такая очистка например запускается WordPress при добавлении комментария или записи.

wp_rel_nofollow( $html )
Добавляет rel="nofollow" ко всем элементам <a> в переданном HTML тексте.
wp_kses_allowed_html( $context )
Возвращает массив допустимых HTML элементов для указанного контекста. См. описание...
balanceTags( $html ) или force_balance_tags( $html )
Закрывает незакрытые HTML теги, чтобы вывод не вызвал ошибку.
balanceTags() срабатывает только если включена спец настройка в настройках сайта «WordPress должен исправлять некорректный XHTML-код автоматически.». А вот force_balance_tags() срабатывает всегда!
tag_escape( $tag_name )
Очищает название HTML тега. Удаляет все символы кроме a-zA-Z0-9_:. Переводит строку в нижний регистр.
sanitize_html_class( $class, $fallback )
Подготавливает текст для использования его в html атрибуте class: удаляет все неподходящие символы. Удаляет всё кроме A-Za-z0-9_-. Если результат пустой, то в $fallback можно установить значение по умолчанию.

Email

is_email( $email )
Проверяет является ли переданная строка e-mail адресом. Вернет true или false.
sanitize_email( $email )
Очищает e-mail: убирает недопустимые символы из e-mail адреса.

URL (ссылки)

esc_url( $url )

Очищает УРЛ для использования его в тексте, изменяет неправильные и удаляет опасные символы. Не пропускает URL, если в нем указан протокол не из белого списка (http, https, ftp, ftps, mailto, news, irc, gopher, nntp, feed, и telnet).

Используйте эту очистку при выводе любого URL на экран (в тексте, в атрибутах или где-либо еще).

Также, эта функция кодирует некоторые спец. символы, поэтому её рекомендуется использовать при генерации строк для (X)HTML или XML документов. Кодирует амперсанд (&) и одинарные кавычки в их числовые сущности (&#038, &#039).

esc_url_raw( $url )
Очищает УРЛ для безопасного использования. В отличии от esc_url(), не очищает для безопасного вывода на экран. Используйте, когда нужно получить НЕкодированный URL, например: в запросах к БД, при редиректах, в HTTP запросах.
urlencode( $url )

PHP функция, которая кодирует URL, так что его можно использовать как параметр запроса. Т.е. заменяет все возможные символы URL (&, /, пробел и т.д.) на их сущности. Чтобы вернуть такой URL в прежнее состояние, используйте urldecode().

Эта функцию используется не для вывода URL на экран, а для использования URL где-то в запросе, чтобы PHP не мог интерпретировать строку как URL. К примеру, если обработать http://site.ru/one, то получим http%3A%2F%2Fsite.ru%2Fone — это уже не URL, а строка с набором символов...

urlencode_deep( $array )
Обрабатывает все элементы переданного массива функцией urlencode().

XML

XML документы, в отличии от HTML, понимают лишь некоторые спецсимволы: &apos;, &amp;, &gt;, &lt;, &quot;. Поэтому для вывода текста для XML документов, в WordPress есть функция:

ent2ncr( $text )
Конвертирует строковые сущности в их числовые значения: &rsquo; станет &#8217;.

JavaScript

esc_js( $text )

Подготавливает строку для использования в JavaScript. Полезна при использовании однострочного JS кода (в HTML атрибутах, например onclick='…'). Строка должна быть заключена в одинарные кавычки.

Или вот такой пример:

<script>
	var = '<?php echo esc_js( $js ); ?>';
</script>

Файловая система

validate_file( $filename, $allowed_files )

Используется для защиты от атаки «выход за пределы каталога» (directory traversal attacks). Или, когда нужно проверить находится ли файл в белом списке (параметр $allowed_files).

Возвращает: 0 - если проверка пройдена. И вернет число ошибки (> 0), если есть ошибка. Это нестандартный подход, поэтому будьте внимательны при написании кода!

Для проверки функции нужно передавать только абсолютные пути без ../ или ./. Например /etc/hosts пройдет проверку, а ./hosts - не пройдет.

Атака выхода за директорию выглядит примерно так, отправляется такой запрос: http://test.ru/?dir=../../Windows/system.ini и если сервер или код не защищен, то можно получить содержимое файла system.ini.
Такие атаки не частые судя по информации из google и относятся к средней степени риска. Но игнорировать их все же не стоит...

HTTP Headers (URL)

Атаки с разделением ответов HTTP (header splitting attacks) создаются на стороне клиента, а не сервера и поэтому их очень трудно выловить. WordPress иногда добавляет данные отправляемые пользователем в HTTP заголовок, но чтобы избежать такие атаки, передаваемые заголовки проверяются в белом списке и все опасное вырезается.

Для очистки потенциально опасных заголовков в WordPress предусмотрены 2 функции редиректа:

wp_redirect( $location, $status = 302 )
Безопасный способ перенаправить пользователя на любой URL. Функция проверяет чтобы итоговый HTTP запрос не содержал опасных данных.
wp_safe_redirect( $location, $status = 302 )
Еще более надежный способ редиректа, при котором перенаправление возможно только на разрешенные в белом списке хосты.
wp_sanitize_redirect( $location )
Очищает указанный URL для использования при перенаправлениях.

Коротко об атаках с разделением ответов HTTP

В ходе данной атаки, также называемой CRLF-инъекцией, уязвимый веб-сервер отвечает на специально сформированный вредоносный запрос ответом HTTP, который интерпретируется как два отдельных ответа вместо одного. Это становится возможным в случаях, когда в заголовках ответа HTTP используются данные, введенные пользователем без дополнительных проверок. Взломщик может спровоцировать ситуацию, при которой браузер жертвы атаки интерпретирует добавленный заголовок как ответ на второй запрос, при этом специально сформированные данные будут отображены и, возможно, добавлены в кэш браузера.

Для реализации разделения ответов HTTP с использованием уязвимого сервера, взломщик выполняет следующие действия:

  • Обнаруживает возможности для ввода данных пользователем с возможностью добавления этих данных в заголовок HTTP.

  • Формирует вредоносную строку для завершения запроса от приложения и добавления своего собственного запроса с необходимыми данными в заголовке.

  • Принуждает жертву атаки к отправке двух запросов серверу. Первый запрос содержит специально сформированные вредоносные данные в составе заголовка HTTP, а второй - запрос от приложения, поэтому браузер жертвы атаки интерпретирует разделенный ответ таким образом, что он считается принадлежащим второму запросу.

Подробнее читайте тут.

База данных

$wpdb->insert( $table, $data )
Вставляет данные в указанную таблицу базы данных.
Важно: Значения массива $data должны быть с экранированным слэшами (\)! Функция убирает слэши и она уберет нужные слэши, если они есть, но не экранированы. Ключи массива - это колонки, а значения - значения.
$wpdb->update( $table, $data, $where )

Обновляет данные в указанной таблице базы данных. $where — это условие, в каких именно строках производить замену. Множественные WHERE условия объединяются с помощью AND.

Как и $wpdb->insert значения массивов $data и $where должны быть с экранированным слэшами! Ключи массива - это колонки, а значения - значения.

$wpdb->update( 'my_table',
  array( 'status' => 'не \' безопасные данные', 'title' => $_GET['title'] ),
  array( 'id' => 123 ), // условие какую строку обновлять.
);
$wpdb->prepare( $format, $value1, $value2, ... )

Очищает запрос: безопасно заменяет заполнители (placeholder) в $format указанными в $value1, $value2, ... значениями.
$format — это строка на подобие sprintf(), в которой можно указать только следующие заполнители: %s, %d и %f. Нет необходимости в кавычках для строк (%s). prepare() добавит кавычки сам, т.е. foo=%s станет foo='значение'. Но если кавычки есть, ничего страшного, функция понимает такую конструкцию.

Работает на основе wpdb::_real_escape( $string ) с предварительной обработкой форматирования.

$wpdb->get_var( $wpdb->prepare(
  "SELECT something FROM table WHERE foo = %s and status = %d",
  $_GET['name'],  // 'не \' безопасные строка' (функция сделает очистку сама)
  $_GET['status'] // 'не безопасное число (функция сделает очистку сама)
) );
esc_sql( $sql )
Экранирует строку или массив строк для использования в SQL запросе. Опирается на функцию addslashes().
$wpdb->prepare() предпочтительнее, потому что исправляет некоторые ошибки форматирования (кавычки).
Работает на основе wpdb::_real_escape( $string ).
$wpdb->escape_by_ref( &$text )
Не возвращает данные, т.к. они передаются по ссылке. Данные очищаются «на лету».
Работает на основе wpdb::_real_escape( $string )
$wpdb->esc_like( $text )

Подготавливает строку для использования в LIKE части SQL запроса. Обработанная строка должна быть еще обработана одной из функций очистки!

$link = $wpdb->esc_like( $link ); // подготовим строку для LIKE аргумента
$link = esc_sql( $link );         // очистим переменную
$link = '%' . $link . '%';        // создадим полную переменную поиска LIKE
// $link готова для использования в SQL запросе.
sanitize_sql_orderby( $orderby )
Проверяет можно ли использовать переданную строку в ORDER BY части SQL запроса.
sanitize_title_for_query( $title )
Подготавливает строку для использования её в качестве ярлыка (slug) в SQL запросе. Очистка от инъекций делается отдельно. Подразумевается что это название чего-либо: заголовка, имени файла и т.д.

Устарелые

$wpdb->escape( $text )
Запрещена с версии 3.6. Используйте esc_sql() или $wpdb->prepare().
like_escape( $string )
Запрещена с версии 4.0. Используйте $wpdb->esc_like().

Остальные функции

Остальные функции очистки, которые не вошли в предыдущие списки...

sanitize_meta( $meta_key, $meta_value, $meta_type )

Очищает значение мата данных. Сама функция ничего не делает, а примеряет фильтр "sanitize_{$meta_type}_meta_{$meta_key}", через который разные мета данные можно очистить по-разному.

Примечательно то, что эта функция используется во всех функциях при добавлении/обновлении метаданных WordPress: update_*_meta() или add_*_meta(). Т.е. использовать её напрямую обычно нет смысла, но вот использовать хук который она обрабатывает очень удобно при обновлении любых метаданных...

sanitize_term( $term, $taxonomy, $context )
Очищает все поля элемента таксономии с помощью функции sanitize_term_field().

Проверка и очистка в PHP: filter_var()

Выше были рассмотрены функции WordPress. Но разработчики же как-то жили и живут без WordPress? И заглянув под ширму PHP, можно найти очень интересную для проверки и очистки данных функцию: filter_var(). Это PHP функция и поэтому она будет работать везде и зачастую быстрее прочих аналогов.

filter_var() - проверяет или очищает значение указанной переменной по указанным параметрам.

Возвращает

Отфильтрованные данные или FALSE, в случае неудачной проверки, фильтрации.

Использование

$var = filter_var( $var, $filter, $options );
$var(разное) (обязательный)
Переменная, которую нужно очистить, проверить.
$filter(число)

ID фильтра который будет использоваться для проверки, очистки. Такие ID хранятся в виде чисел в предопределенных константах PHP. Их полный список можно посмотреть тут:

По умолчанию указана константа FILTER_DEFAULT, значит что никакой фильтр не применяется.
По умолчанию: FILTER_DEFAULT

$options(массив/константа)

Различные параметры или флаги фильтрации. Константой или комбинацией констант через | (ИЛИ). Или может быть массивом, который поддерживает всего 2 ключа: array('options'=>..., 'flags'=>... ).

Все возможные флаги смотрите здесь: флаги, используемые a filter_var.

Пример опций фильтрации, с указанием констант:

// вернуть null при неудачной проверке
filter_var('site.ru', FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE ); //> null

// или флаги можно указать в элементе массива flags
filter_var('site.ru', FILTER_VALIDATE_URL, array('flags'=>FILTER_NULL_ON_FAILURE) ); //> null

Пример опций фильтрации, с указанием массива:

// массив параметров фильтрации
$options = array(
	'options' => array(
		'default' => 'http://site.ru/info', // вернется при неудачной проверке
	),
	'flags' => FILTER_FLAG_PATH_REQUIRED, // флаг: Включает содержание пути в URL в качестве необходимого условия.
);
$var = filter_var('http://site.ru', FILTER_VALIDATE_URL, $options );

echo $var; //> http://site.ru/info
// т.е. вернулось указанное значение по умолчанию, 
// потому что флаг говорит, что у URL должен быть путь

По умолчанию: null

Примеры filter_var()

#1 Демонстрация работы

// email
$email = filter_var('bob@example.com', FILTER_VALIDATE_EMAIL); //> bob@example.com
$email = filter_var('bob@example', FILTER_VALIDATE_EMAIL); //> false

// url
$url = filter_var('http://site.ru', FILTER_VALIDATE_URL); //> http://site.ru
$url = filter_var('site.ru', FILTER_VALIDATE_URL); //> false

$url = filter_var('http://site.ru/path', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED); //> http://site.ru/path
$url = filter_var('http://site.ru', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED); //> false

#2 Проверим является ли строка IP адресом

if( filter_var( 'foo', FILTER_VALIDATE_IP ) )
	echo 'это IP';
else
	echo 'это не IP';

// выведет: 'это не IP'

if( filter_var( '123.111.222.123', FILTER_VALIDATE_IP ) )
	echo 'это IP';
else
	echo 'это не IP';

// выведет: 'это IP'

// по-другому можно записать так
filter_var( 'foo', FILTER_VALIDATE_IP ); //> false
filter_var( '123.111.222.123', FILTER_VALIDATE_IP ); //> true

-

Информация этого руководства взята из официального источника и личного опыта.

Валидация данных в WordPress 5 комментариев
  • campusboy1381 cайт: wp-plus.ru
    @

    Как долго я ждал такого материала в сети, чтоб как шпаргалка, и он появился на любимом сайте. Тимур, мой тебе поклон!

    Ответить1.1 года назад #
    2
  • campusboy1381 cайт: wp-plus.ru
    @

    Получается, такой вывод тоже надо чистить?
    get_post_meta($post->ID, 'my_html_block', true)
    Она же не напрямую выводит на экран.

    Ответитьгод назад #
    • Kama4197

      Да надо! Произвольные поля все надо, в идеале, кроме вариантов когда ты точно знаешь что не обязательно: когда они при записи очищаются, но там много способов, когда можно ошибиться и пользователь может ввести вредные данные...

      Ответитьгод назад #
      4
  • Otshelnik-Fm192 cайт: across-ocean.otshelnik-fm.ru

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

    -1

Здравствуйте, !

Ваш комментарий