Решил сохранить для истории один рабочий момент, который вчера поднял мне настроение до конца рабочего дня (это достаточно редкое явление). Это прямо самое настоящее расследование, обожаю такие дела распутывать.
Мою легенду раскрыли. Двери захлопнулись с обеих сторон. А потом был сплошной свинцовый дождь...
Тьфу ты, всё началось иначе. В почту упала таска: так и так, на одной странице выводятся одни цифры партнёрских скидок, на другой должна выводиться только самая большая из них всех, но выводится какая-то другая. Кто-то похитил скидку!
Менеджер хотел, чтобы я навел порядок в этом хлеву. В конце концов, за часть этой грязи отвечал я сам.
Я надел детективную шляпу, проверил обойму инструментов, и открыл страницу в отладчике. Детективное чутьё сразу указало мне на улику: ng-bind-html="showPartnerDiscount". Явный след Грязного Ангуляра, мелкого узбека-форточника.
Поиск по скриптам страницы вывел меня на прототип этой функции. Так и есть, Ангуляр сидел в своей юрте, делая невиноватое лицо.
- Признавайся, это ты украл скидку? - прямо спросил я его, приставив ему ко лбу дуло отладчика. Намек был таким же туманным и неопределенным, как пуля в сердце.
- Ничего не знаю, ничего не докажешь!
- Так... а это что? - я ловко вытащил у него из кармана цепочку стековых запросов, последний из которых обращался аяксом на какой-то URL. После предъявленных улик и внушительной зуботычины Ангуляр раскололся и сдал мне пароли.
- Больше ничего не знаю, начальник, я просто курьер!
Предупредив форточника, чтобы не попадался мне больше, я отправился на охоту за крупным зверем. Кошмарный лабиринт темных улиц простерся передо мной, а я стоял в самом центре его. По найденному адресу меня ждала дверь с окошком. Я постучал.
- Пароль? - донеслось из-за двери.
Я назвал один из странных кодов, отобранных у форточника. Окно приоткрылось, и из него выпал JSON с цифрами... в которых не хватало того самого процента.
Похоже, преступник скрывается внутри.
Я постучал ещё раз.
- Пароль? - снова спросил меня тот же голос.
- Отдел веб-разработки, внутреннее расследование - я предъявил токен пропуска. Дверь тут же распахнулась, впуская меня в тёмные лабиринты сервера.
Поиск скрипта, отдающего JSON не занял много времени, местные знают, кто тут власть. Однако сам скрипт...
Это было порождение истинно сумашедшего ума. 13 килобайт парсерного скрипта, генерирующего дичайший SQL-запрос в базу. Неужели придётся распутывать эту лапшу? Я бы засмеялся, если бы помнил как это делается.
Но и тут мне на помощь пришёл многолетний опыт: не правда ли, метка get_max_discount выглядит очень подозрительно? Посмотрим на код сохранённой функции, скрывающейся за этим именем:
SHOW PROCEDURE CODE get_max_discount - набрал я в консоли.
- Извините, сервер собран без возможности отладки - ответили мне.
Так, попробуем зайти с другой стороны:
SELECT ROUTINE_DEFINITION FROM information_schema.ROUTINES WHERE ROUTINE_NAME='get_max_discount'
И белые буквы кода вывалились прямо в чёрную консоль терминала. Однако - не полностью, я до сих пор ничего не знал о параметрах этой функции. Но это уже что-то.
Может быть, попробовать восстановить SQL из pp-кода? Чёрт, для такой грязной работы ещё не придумано инструментов, пришлось засучить рукава, и вручную разобрать нагромождения условных переходов и ужасные синтаксические конструкции парсера.
Собирать улики стало поздно ещё пару сотен патронов назад. Я так давно миновал точку невозвращения, что забыл, как она выглядит.
Это заняло пять чашек кофе, но в результате я получил рабочий запрос, состоящий, в основном, из кучи JOIN и той самой функции get_max_discount.
Анализировать запрос прямо на боевом сервере? Каждый убранный JOIN приведёт к увеличению размера выборки и завесу сервера, за такое по головке не погладят, да и нет тут инструментов. Нужно перетаскивать к себе в локальную берлогу, и там уже ковырять. Но как перетащить функцию, у которой мы не знаем входных и выходных параметров?
Смотрю на вызов внимательнее:
get_max_discount(CONCAT(disct_visa1,',',disct_visa2,',',disct_visa3,',',disct_mc1,',',disct_mc2,',',disct_mc3,',',disct_amex1,',',disct_amex2,',',disct_amex3,',',disct_diners2,',',disct_diners3,',',disct_discover))
CONCAT - это строка. Размеры параметров примерно известны, на выходе функции тоже должна быть строка. Возьмём с запасом:
CREATE FUNCTION get_max_discount(_discounts CHAR(255)) RETURNS VARCHAR (255)
Переношу и проверяю:
SELECT get_max_discount('10,15,15,10,15,15,10,15,15,15,15,10') as md from any_table limit 1
>15
Функций неуиновен. Дождь смыл след впереди.
И тут меня осеняет. Я снова лезу на сервер, создаю модифицированный парсерный обработчик, выводящий не JSON, а сгенерированный SQL (и почему я не додумался до этого сразу? Все мы делаем ошибки.). На тот же запрос скрипт генерирует вызов функции уже совсем с другими параметрами:
get_max_discount(CONCAT(disct_visa1,',',disct_mc1,',',disct_amex1,',',disct_discover))
значения берутся только для части скидок...
части скидок...
части...
Открываю изначальную страницу, а там маленькая, неприметная выбиралка. Показывающая только часть скидок. Убираю её - все скидки на положенных местах.
Никакого преступления не было, ну разве что убито несколько часов времени из-за невнимательности менеджера.
Повернуться и уйти, скрыться из города... Это было бы самым мудрым решением. Я был не настолько мудр.