Pull to refresh

Своя браузерка — путь мыши: Теория

Reading time7 min
Views5.9K
Итак, вы решили поднять свою браузерку.

Вы понимаете все сопутствующие нюансы и принципы поведения Администратора браузерки: habrahabr.ru/post/249625
Вы чётко осознаёте, что единственный разумный вариант, зачем в это стоит впрягаться — это саморазвитие, причем это не самый лучший путь: habrahabr.ru/post/249705

Мыши кололись и плакали, но продолжали жрать кактус…

Окей. В ваши цепкие лапки попал какой-нибудь скачанный из инета движок браузерки. В лучшем случае — это будет опенсорс с хорошим количеством форков (т.е. свободный от «детских проблем»). Возможно это будет т.н. «дамп» — т.е. слитый с живого коммерческого сервера движок с дампом БД. Но вероятнее всего — это будет очередная «поделка на тему». Вот на последнем случае мы и остановимся — потому что минимально необходимые действия общие для всех трёх случаев.

Здесь я неявно предполагаю, что ваш движок базируется на PHP+MySQL. Именно для этой связки я и буду вести дальнейший рассказ. Если у вас ВДРУГ Python+PostgressSQL — ничего страшного. Фундаментальные принципы работы с такими движками идентичны — различается лишь реализация.

Что же вам нужно сделать в первую очередь с движком? Ответить на этот вопрос не так-то просто, не ознакомившись с основными принципами работы с данными внутри браузерного движка (да и вообще — любого сайта или даже любой программы). Принципов этих немного и они весьма просты.

Данные, пришедшие от пользователя — токсичны


НЕ ДОВЕРЯЙТЕ ПОЛЬЗОВАТЕЛЬСКОМУ ВВОДУ!
Все данные, исходящие от пользователя по определению считаются «токсичными» — т.е. неправильными, невалидными или даже злонамеренными до тех пор, пока не будет доказано обратное. Это значит, что все данные от пользователя нужно проверять на стороне сервера.

Вы думаете, что у вас есть чудесный валидатор на JavaScript, который не пропустит некорректные данные к бэкенду? ОШИБКА! Любой запрос от фронтенда (та часть, что непосредственно видна пользователю и отвечает за взаимодействие с ним) вашего движка можно фальсифицировать — поэтому бэкэнд (та часть, которая производит фактическую обработку данных на стороне сервера и записывает изменения в БД) ОБЯЗАН всегда повторять расчёты фронтенда, что бы быть уверенным, что вам не скормили туфту.

Нормализация данных


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

Ну, например, у вас есть последовательность символов, которая по факту является многострочным текстом. Вроде бы ничего сложного и всё очевидно, так? ОШИБКА! Строка может быть получена из внешнего файла.

Тут сразу начинаются пляски с бубном по поводу символа перевода строки. Ведь в разных системах конец строки кодируется по-разному. Файлы Юникс-подобных систем отбивают новую строку символом LF (Line Feed, ASCII 0x0A). Старые системы (МакОСь до 9-й версии, Коммодор и ещё некоторые) обожали заканчивать строку символом CR (Carriage Return, ASCII 0x0D). Самая десктопная ОСь Винда (как наследник MS-DOS) обожают комбинацию CR+LF — и именно в таком порядке!

И тут появляется великий HTML, для которого всё, что за рамками тэга «pre» (и других подобных тегов) — суть одна строка. А разбивка на строки производится тегом <br>. Который, конечно, правильно записывать как
. Допускается так же и <br/>,
итд

Проблема с нормализацией строк не зря упомянута первой. Обычно неправильная нормализация строк не даёт критичных ошибок — зато чаще всего видна игрокам. Кому приятно вместо отформатированного по параграфам текста увидеть что-то вроде
Строка 1\r\nСтрока 2\r\nСтрока 3? Или даже Строка 1
Строка 2
Строка 3
, что случается при одновременной ошибке в нормализации данных и форматировании вывода.

Числа не лишены аналогичных проблем. Казалось бы — что может быть проще числа? intval() и floatval() (и их аналоги в JavaScript — впрочем, это отдельный разговор. JS позволяет выстрелить себе в ногу множеством изощрённых путей...) должны решить все проблемы, правда же? ОШИБКА!

Какое число получится в переменной, если в PHP-бэкэнде написать intval(0123)? Думаете — 123? Ну, тогда вперед, читать мануал: php.net/manual/ru/language.types.integer.php

Отдельная песня — числа с плавающей точкой в PHP и их сравнение. Здесь не урок по PHP — поэтому опять же отошлю к документации: php.net/manual/ru/language.types.float.php Особенное внимание предлагаю уделить внимание вставке «Точность чисел с плавающей точкой» и подразделу «Сравнение чисел с плавающей точкой».

Вряд ли это будет актуально на первом этапе развития, но для понимания важности нормализации данных не могу не упомянуть об отдельном интересном вопросе идентификаторов в БД. Ну, это те самые веселые числа, объявленные как BIGINT(20) в БД и определяющие уникальность каждой записи.
Чтобы полностью понять суть проблемы — опять сошлюсь на документацию PHP php.net/manual/ru/reserved.constants.php Искать в тексте ключевое слово PHP_INT_MAX. Посчитать количество символов. Сравнить с описанием структуры БД выше. Подсказка — в MySQL данное объявление означает число с 20 значащими символами до запятой. При неаккуратной работе с таким идентификатором в PHP- или JS-коде он легко может обратиться во float с потерей точности.

Какой вывод можно сделать из всего вышенаписанного? Данные должны быть НОРМАЛИЗИРОВАНЫ — т.е. приведены к некоей стандартизированной форме, которая будет восприниматься вашим движком отныне и везде. Обычно автор(ы) движка неявно выбирают какую-то одну форму нормализации. К сожалению — те же автор(ы) соблюдают её далеко не всегда и не везде.

Экранирование выводимых данных


Вы удивитесь, насколько часто игре надо будет выводить данные для игроков! Здесь нас так же поджидает куча интересных откровений.

Вывод в HTML


Самое главное правило таково — ЛЮБОЙ вывод в HTML неконтролируемых данных должен производиться исключительно через функцию htmlentities() php.net/manual/ru/function.htmlentities.php! Еще раз подчеркну — ЛЮБОЙ! БЕЗ ИСКЛЮЧЕНИЙ!

Здесь нужно заметить, что данные в движке делятся на «контролируемые» и «неконтролируемые». К первым относятся такие сущности как простые строки локализации, гарантированно нормализованные целые числа, гарантированно нормализованные однострочные строки и… пожалуй — всё. Все остальные данные являются «неконтролируемыми» и должны проходить через мелкое сито htmlentities(). Да, это кажется излишним усложнением — но в будущем это как минимум убережет от порчи вида игры, а как максимум — в определенной мере гарантирует невозможность заражения компьютера игрока враждебными скриптами (100% гарантию даёт только «Госстрах»).

Здесь стоит сделать отступление и подробно поговорить о каждом типе контролируемых данных.

Контролируемые данные в HTML


Начнём с самого простого — с гарантированно нормализованных целых чисел. К ним относятся переменные $variable, которые на проверку is_int($variable) отвечают 'true'. Тупо. Прямо. В лоб. 100% гарантия, что данные переменные при выводе будут состоять только из десятичных цифр, не содержат ничего постороннего и одинаково красиво выглядят в HTML и JS.

Простые строки локализации — это строки из текущей локали, которые состоят из одной строки, не содержат HTML-тэгов, не содержат спецсимволов HTML типа < > " ', переводов строки, не являются регекспами и не являются шаблонами. Здесь тоже вроде всё понятно с первого взгляда. Вроде бы строки локализации полностью контролирует разработчик движка. Вроде бы никаких подводных камней быть не должно — и все эти уточнения излишни… WRONG!

Типичнейшая ошибка — использование шаблонов строк типа «Начало строки %s остальная строка» с последующим использованием функции sprintf(). Мы помним, что данные у нас — должны быть нормализированы, т.е. приведены к стандартному формату. Однако нигде не сказано, что нормальный формат хоть для чего-то является безопасным! Нормализация формата переменных всего лишь гарантирует единообразие вида данных, обрабатываемых в разных кусках кода — и не более того. Являются ли данные безопасными для вывода в HTML или (тем более!) для записи в БД — это нам неизвестно (вообще-то известно — по определению не являются). Поэтому прямой вывод данных в шаблон без указания дополнительных модификаторов — недопустим. Более того — не всякий модификатор гарантирует безопасный вывод данных.

Замечу — тут мы говорим именно о безопасности вывода, а не о корректности. Например, вывод переменной в шаблон "%d" является безопасным, но не является корректным — если, например, на вход будет подана строка, не сводимая к целому. Тут опять же не могу не отослать к документации: php.net/manual/ru/function.sprintf.php

По нормальным делам все строки локализации должны быть гарантированно простыми. Однако идеал — это то, к чему нужно стремится, но и одновременно то, что является недостижимым в реальности.

Практические аспекты и расчёты трудозатрат говорят, что иногда приходится делать и не-простые строки локализации — например, добавлять HTML-форматирование сразу в строку. Я считаю — что это приемлемо до тех пор, пока разработчик осознаёт все риски и контролирует употребление неконвенциональных строк локализации.

Почему гарантированно нормированные строки должны содержать только одну строку/параграф — должно быть уже понятно из прочитанного выше. Но на всякий случай повторюсь — многострочные строки (даже нормированные) требуют разного отношение при выводе в HTML, JS или при сохранении в БД.

Неконтролируемые данные в HTML


Выше мы рассмотрели три вида контролируемых данных, которые (с оговоркам) могут выводится напрямую в HTML минуя функцию htmlentities(). Все остальные данные В ОБЯЗАТЕЛЬНОМ ПОРЯДКЕ должны проходить через вышеупомянутую функцию! Однако здесь есть НЮАНСЫ.

Фактически, «неконтролируемыми данными» являются пользовательские данные, либо строки, не подходящие под вышеописанные критерии. Как работать с пользовательскими данными — будет подробно описано в следующей статье. А работа со строками движка (в частности — локализация) — достаточно деликатная тема.
В зависимости от качества исходного продукта, принципы работы со строками, источниками которых является сам движок, могут сильно отличаться. Я могу дать лишь рекомендации — которые ни в коем случае не должны являться догмой. Очень многое зависит от того, как движок описывает многострочные данные вообще и строки локализации — в частности.

1. Если движок использует для разделения строк в тексте символы CR, LF или комбинацию CR+LF — используйте сначала htmlentities(), а затем — PHP-функцию nl2br() php.net/manual/ru/function.nl2br.php
2. Если движок использует для разделения строк тэг
— выводите данные напрямую, если вы полностью проверили код и уверены в его безопасности
3. Весь вывод шаблонов с использованием форматирования "%s" и аналогичного — всегда ДОЛЖЕН проходить через htmlentities()!

В любом случае ВСЕГДА нужно помнить основополагающий принцип — лучше вывести игроку «некрасивую», но гарантированно безопасную строку, чем в 99,99% выводить «красоту», а в 0,01% — заражать компьютер игрока вредоносным скриптом.

В следующей статье:
— Вывод данных в JavaScript — особенности и приколы;
— Запись данных в БД — как не «попасть» на SQL-injection;
— Как убедится, что данные от пользователя не являются «токсичными»;
— … и многое другое
Tags:
Hubs:
-17
Comments15

Articles

Change theme settings