У меня тут новые приключения, блин! Были перед самым отдыхом! Дописывал я пост про ГидроЛоки и надо было мне поставить туда ссылку на пост про Рогалики, в котором вроде как упоминалась защита от протечек «Нептун», которую мы там в щит ставили. И вот ищу я, значится, этот пост и листаю его в поисках фотки… и вдруг нахожу такую же неожиданность, как и в посте про Генераторы и АВР, который вдруг был-был, а потом неожиданно закончился.
Вот какую:
Пост WordPress, обрезанный в конце из-за неверного SQL-запроса
Пост вдруг заканчивается на символе «%» и обрезается нахрен. Конечно же его остаток безвозвратно теряется, если у вас нет бэкапа. Вот у меня не было! С постом про генераторы пришлось просить наших камрадов найти текст, если у кого в RSS остался, а с постом про Рогалики — рыть публичный Web Archive, где я его таки нашёл. Короче, как обычно: сисадмины делятся на тех, кто ещё не начал делать бэкапы и на тех, кто УЖЕ начал ;) Только вот все мои бэкапы были уже с повреждёнными постами, мать его!
Заинтересовала меня эта тема, и я решил разобраться в двух вещах: построить догадки о том, почему посты так обрезаются (хорошо бы ещё найти, после каких действий) и искать такие посты и вовремя их восстанавливать из бэкапов.
Сначала выдумываем диагностику. Как я заметил — если пост срезается, то у него ещё и комментарии закрываются. Раз в конце поста всегда есть «%» и комменты закрыты — то мы можем составить парочку запросов к базе данных с такими вот условиями (тут «XXX» — это название таблицы posts в вашей базе; обычно все таблицы WordPress называют с каким-нить префиксом, чтобы враги не догадались и не атаковали сайты):
SELECT * FROM `XXX` WHERE `post_type`='post' AND RIGHT(`post_content`, 1)='%';
Чего мы тут делаем? Отбираем только посты (потому что в этой же таблице валяются и ревизии, и вложения) и смотрим, равен ли последний символ поста процентику. Если равен — то это наш клиент, вот:
Запрос для поиска обрезанных в конце постов WordPress
Как я уже говорил, у меня если пост обрезается — то у него ещё и отключаются комментарии. Отключенные комментарии у поста можно проверить таким вот запросом:
SELECT * FROM `XXX` WHERE `post_type`='post' AND `comment_status`='closed';
Этот запрос, скорее всего, не сгодится ибо у вас могут быть и просто посты с закрытыми комментариями, что не является ошибкой. Поэтому удобнее пользоваться первым постом.
Если косяков с постами не будет — то запрос будет пустой:
Запрос для поиска обрезанных в конце постов WordPress (посты не найдены)
Как бы теперь разобраться с тем, какого чёрта такая обрезка происходит?
Давайте посмотрим на текст поста, который идёт дальше, после обрезанного места:
Обрезанный пост восстановлен из архива и продолжается дальше
А там, оказывается, была кавычка и запятая! Хе хе! WordPress отвечает за автоматическое форматирование текста, поэтому конструкции вида «пробел — дефис — пробел» он превращает в длинное тире, а обычные кавычки сам меняет на открывающие и закрывающие. Но на самом деле эти красивые кавычки — это простой символ кавычек:
"
И поэтому моя конструкция текста в посте превратится вот в какую:
",
А этот кусочек ОЧЕНЬ похож на закрывание какого-нибудь SQL-запроса, например запроса на добавление чего-то в таблицу:
INSERT INTO XXX VALUES (231, "Заголовок поста", "Текст поста", "open", "post");
Чуете грёбаный подвох? Если в каком-то текстовом поле будет содержаться наша хитрая конструкция, то запрос MySQL поймёт её так, что у нас кончилось одно поле и остальные данные надо занести в другие, следующие поля. То есть, огрызок текста после кавычек может попадать на поле «comment_status», а WordPress, если не видит в нём значения «open» — считает комменты к посту закрытыми. Во как!
Стоп-стоп! А как же тогда я написал этот пост? Как же он вообще добавился в базу? А очень просто! Для таких вот конструкций применяется экранирование символов — стандартный приём для таких случаев на уровне всех движков баз данных и языков программирования. Наш запрос можно написать вот так:
INSERT INTO XXX VALUES (231, "Заголовок поста", "Текст поста \"в кавычках\", текст дальше", "open", "post");
В этом случае движок поймёт, что кавычки тут надо читать не как конец значения, а как… да как просто кавычки =) Значит, первое, к чему такое может приводить — это чей-то хреновый код. Сам движок WordPress, конечно же, экранирует все символы нормально.
Но у нас есть ещё и знак процента! А в MySQL часто процентами обозначают всякие условия. Скажем, если нам надо найти слово в поле, то его пишут как «LIKE ‘%слово%'». И если кто-то напутает с экранированием символов — то может быть такой вот косяк.
Конечно же виновника мне вычислить не удалось, ибо в обоих случаях посты повреждались как-то сами по себе и я не знал про это. Теперь я буду после каждых обновлений движка и серьёзных перемен прогонять через базу тот поисковый запрос и, если соображу, кто мне так гадит — допишу этот пост! В общем, будьте внимательны!
Проекту исполнилось 15 лет! Поддержать проект материально, проспонсировать проекты Автора или сделать ему подарок можно на этой странице: "Донаты и Спонсорство, Список Желаний".
Вернулся! Писал же что до 25-го ничо делать не будешь. А сам накатал два поста — про Нептуна и WordPress.
По теме: сказать нечего, увы. Забросил сайтоведение еще со времен школы, но в закладки добавлю — оказывается и такое может быть.
Не по теме: напишешь почему «Хочу назад, там жить и работать световиком!» ? =)
После поста о Кипре стало интересно, где же в этот раз был и что ТАКОЕ там нашел)
А я обязан прям исполнять всё, что писал? Вон я ща кааак на ЮТуб вывалю видосов про Medieval Fest — и опять все гавном поливаться будут — те, кто думал что им про щитки будут втирать.
А вот как раз ТАКОЕ и было. Напишу потом пост.
WordPress уже давно имеет устойчивую репутацию самой дырявой CMS-ки в мире. И в то же время каким-то чудом остаётся самой популярной CMS-кой. Парадокс.
А вообще экранирование- это костыль. Правильный путь- это parameter binding (ХЗ как это по-русски) в SQL запросах, и тот же MySQL его умеет с незапамятных времен. Забыть что-то заэкранировать проще простого. Куда надёжнее не использовать динамическое формирование запросов как таковое. Но вот пых-пыхеры, по какой-то причине, настойчиво обходят «правильные подходы» стороной.
Так вроде как сама CMS не дырявая. Больше всего дыр в плагинах было всегда. В самом WP основная уязвимость пёрла через XML-RPC, который тупо лочишь через .htaccess и всё (у меня так и сделано).
А вот кто как плагин напишет — там поди угадай. Поэтому я стараюсь дофига плагинов и фишек не использовать.
Parameter Binding. Я не знаю, как это на MySQL и пока вообще первый раз слышу. А как им собрать какую-нить строку SQL-запроса типа Select/Update? Расскажи общий смысл, а?
CS, это как аргументы функций в программировании. Можно так:
f(123);
а можно так:
int a = 123;
f(a);
А вот мануал на русском про PHP и базы данных: http://php.net/manual/ru/pdo.prepared-statements.php
Ну вот постит, скажем, кто-то новый комент, и нужно добавить его в таблицу с комментариями. Есть у нас три переменных- topic_id, comment_date, comment_text, и нам нужно значения из этих переменных вставить в строку в таблице. В случае с динамическим формированием запросов будет что-то вроде (псевдокод):
sql_stmt = "insert into topic_comments (topic_id, comment_date, comment_text) values(" + topic_id + ", " + comment_date + ", " + escape_sql(comment_text) + ")"; sql_exec(sql_stmt);
В примере выше escape_sql() экранирует спец-символы, и если про него забыть, будут проблемы, как описаны в статье. Альтернативный подход выглядит примерно так (тоже псевдокод):
sql_stmt = "insert into topic_comments(topic_id, comment_date, comment_text) values(?, ?, ?)"; prep_stmt = sql_prepare(sql_stmt) sql_stmt_bind(1, topic_id); sql_stmt_bind(2, comment_date); sql_stmt_bind(3, comment_text); sql_stmt_exec(prep_stmt); sql_stmt_close(prep_stmt);
У такого подхода есть несколько преимущества. Во-первых ничего экранировать не нужно. Текст запроса и значения параметров в базу передаются раздельно, и значения параметров никаким специальным образом не обрабатываются, даже если выглядят как валидные SQL-запросы. Во-вторых код выглядит чище, особенно когда запрос сложный, и параметров много. Ну и в третьих, если нужно выполнить один и тот же запрос но с разными значениями параметров много раз, то синтаксический разбор запроса повторно выполнять не нужно, и код будет работать немного быстрее (опять псевдокод):
sql_stmt = "insert into topic_comments(topic_id, comment_date, comment_text) values(?, ?, ?)"; prep_stmt = sql_prepare(sql_stmt) sql_stmt_bind(1, topic_id); sql_stmt_bind(2, comment_date); sql_stmt_bind(3, comment_text); sql_stmt_exec(prep_stmt); topic_id = another_topic_id; comment_date = another_comment_date; comment_text = another_comment_text; sql_stmt_exec(prep_stmt); topic_id = yet_another_topic_id; comment_date = yet_another_comment_date; comment_text = yet_another_comment_text; sql_stmt_exec(prep_stmt); sql_stmt_close(prep_stmt);
Млять!!! ПОЧЕМУ Я ЭТОГО НЕ ЗНАЛ?!!! Я когда в 2005-7 году поднимал один сайт — я там тупо составлял запросы склеиванием строк и затрахался это делать, потому что пропустишь чего-то в склейке — и пиздец!!
Это ж как удобно-то! Я примеры посмотрел и взял на заметку!
Я использую в PHP класс PDO:
$db = new PDO("my_server_url"); if ($db === NULL) ... $db->exec("SET SESSION timezone TO 'UTC'"); $st = $db->prepare("SELECT something FROM table WHERE p1 = :v1 AND p2 = :v2"); if ($st === FALSE) ... if ($st->execute(['v1' => $v1, 'v2' => $v2]) === FALSE) ... $res = $st->fetch(PDO::FETCH_ASSOC); ...
@CS, не могли бы вы сделать статью описывающую, как правильно подключать к щитам вводные кабели, речь про алюминиевые и бронированные (типа АВБШв), а то не очень понятно, можно ли как-то сделать, чтобы постоянно не подтягивать винты (закрепил на винт в гребенке ноль (4 мм^2), так через два года там фейрверк начался).
b-s-a Ну, классы… ООП местами слишком грузит всех и вся. Мы уже и так докатились, что у нас щас стало нормой, что какое-нить приложение, которое показывает три окошка, весит под 150-200 мегабайт. Давайте и PHP нагружать. А чё? Нагрузим PHP, он будет тормозить, а мы возьмём сервера позлее, и тады тормозящий PHP на них будет работать так же быстро, как и до того, когда мы его не нагружали =)
Но идея понятна.
Пост сделать? Я уже в одном посте тебя ругал за то, что ты не читаешь, а пишешь, так тут продолжу. А наконечники ТМЛ/ТА — не? Не годятся?
Поста не будет. Особенно по таким запросам вида «я ничё не нашёл, сделай пост». И про что будет пост? Про «как подключать жирные кабели к щитам для дибилов?». Но я для дибилов не пишу =)