Документация
/
Рецепты
/

Подстановка значений в шаблон на стороне сервера

Подстановка значений в шаблон на стороне сервера

Pro
v6.10.0+

Как подставлять значения, отрисованные на сервере (например, плейсхолдеры в стиле шаблонов Go `{{ .Field }}`), в JavaScript с VM-обфускацией, не ломая процесс обфускации.

Проблема

Ваш бэкенд (Go, Rails, Django, PHP, …) отдаёт JavaScript-файл, часть содержимого которого нужно формировать для каждого запроса: эндпоинт API, список фича-флагов, начальное состояние, идентификатор сборки, nonce. Вы обфусцируете JS с vmObfuscation: true, но шаблонизатор должен подставить значения в плейсхолдеры обфусцированного результата после завершения обфускации. reservedNames и reservedStrings оставляют плейсхолдер видимым в результате, чтобы его можно было заменить. Без них строковый плейсхолдер попадает в байт-код VM, а плейсхолдер-идентификатор выводится в нескольких переписанных местах, так что шаблонизатор либо не находит, что заменять, либо ломает результат.

Возможно, лучше загружать значения как данные

По возможности вообще не редактируйте защищённый результат: загружайте конфигурацию времени выполнения как данные до запуска защищённого бандла. Держите её в отдельном скрипте или получайте с эндпоинта, требующего аутентификации. Так защищённый результат остаётся нетронутым, и vmSelfDefending можно не выключать. Данные, переданные в браузер, в любом случае видны этому браузеру.

JavaScript

Используйте описанные ниже паттерны, только когда значения действительно нужно подставлять в обфусцированный файл.

О примерах на Go: они выполняют простую замену строк (strings.ReplaceAll / strings.NewReplacer) в результате обфускации и сами занимаются экранированием, показанным для каждого паттерна. Они не пропускаются через пакет Go html/template - он добавил бы сверху собственное контекстное экранирование JavaScript, и полезная нагрузка была бы экранирована дважды. Если вы всё же рендерите через html/template, уберите ручное экранирование и позвольте движку экранировать один раз.

Какую опцию выбрать?

Значение с сервера…Что использовать
Готовое JS-значение (массив, объект, число, логическое значение - любое значение JSON)Паттерн 1 - reservedNames + плейсхолдер-идентификатор
Строка (типичный случай - отрисованный JSON, идентификатор сборки, nonce)Паттерн 2 - reservedStrings + плейсхолдер в шаблонном литерале
Строка, когда код должен оставаться на ES5 или окружающий API требует строкового литерала в кавычкахПаттерн 3 - reservedStrings + плейсхолдер в строке в кавычках

Подстановка после обфускации несовместима с vmSelfDefending: true. См. Замечания о совместимости в конце этого рецепта.

Паттерн 1 - reservedNames с плейсхолдером-идентификатором

Лучше всего подходит для: подстановки готовых JS-выражений (массивов, объектов, чисел, …) без каких-либо забот об экранировании кавычек.

Выберите характерный идентификатор, который никогда не пересечётся с реальным кодом, обращайтесь к нему напрямую и добавьте соответствующее ему регулярное выражение в reservedNames. При VM-обфускации идентификатор проходит через массив зарезервированных выражений и попадает в результат дословно, например:

JavaScript

Замените идентификатор полным выражением, сериализованным в JSON. Не вставляйте значение в кавычки или обратные кавычки: идентификатор находится не внутри строкового литерала, поэтому JS-движок разбирает подставленное значение как обычное выражение. Используйте надёжный сериализатор, экранируйте <, если скрипт встраивается в HTML, и проверьте значения, содержащие кавычки, обратные слеши, переводы строк, обратные кавычки и ${...}.

JavaScript

Паттерн 2 - reservedStrings с плейсхолдером в шаблонном литерале

Лучше всего подходит для: шаблонизаторов в стиле Go / Jinja, требующих разделителей вроде {{ .Field }}, которые оказываются допустимым JS-текстом, если заключить их в обратные кавычки.

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

Исходный код

JavaScript

Опции обфускатора

Интерфейс

Чтобы зарезервировать плейсхолдер {{.Config.FeatureFlags}} из исходного кода выше, добавьте это регулярное выражение в поле Reserved Strings с одинарными обратными слешами. Интерфейс сохраняет значение как есть, поэтому, в отличие от JS-кода, обратные слеши не удваиваются:

Текст

Поле Reserved Strings в интерфейсе обфускатора с регулярным выражением, введённым с одинарными обратными слешами

API

JavaScript

После обфускации

JavaScript

Плейсхолдер сохраняется байт в байт, вместе с окружающими обратными кавычками.

Подстановка на стороне сервера

Замените {{.Config.FeatureFlags}} на JSON-строку, экранированную для шаблонного литерала. Внутри обратных кавычек не нужно экранировать ", но обратный слеш, обратная кавычка или ${ в данных всё равно изменят или завершат литерал, поэтому экранируйте эти три последовательности:

Код

Во время выполнения: JSON.parse(`{"newCheckout":true,"darkMode":false}`) - работает.

Почему для строковых значений лучше шаблонные литералы, а не одинарные или двойные кавычки? В обратных кавычках не нужно экранировать " внутри JSON-данных. Это важно, потому что большинство значений, отрисованных на сервере, - это JSON, а в JSON полно двойных кавычек. С плейсхолдером в двойных кавычках пришлось бы экранировать каждую внутреннюю " (см. паттерн 3); с обратными кавычками экранировать нужно лишь редкие обратные слеши, обратные кавычки и ${.

Паттерн 3 - reservedStrings с плейсхолдером в строке в кавычках

Лучше всего подходит для: исходного кода, который должен оставаться на ES5 (без шаблонных литералов), или случаев, когда окружающий API ожидает обычный строковый литерал.

Плейсхолдер - это строковый литерал в одинарных или двойных кавычках. При VM-обфускации он проходит через массив зарезервированных строк, который выводится как JS-массив, сериализованный через JSON.stringify, - всегда в двойных кавычках, независимо от стиля кавычек во входном коде.

Исходный код

JavaScript

Опции обфускатора

Интерфейс

Чтобы зарезервировать плейсхолдер {{.Page.Tags}} из исходного кода выше, добавьте это регулярное выражение в поле Reserved Strings с одинарными обратными слешами. Интерфейс сохраняет значение как есть, поэтому, в отличие от JS-кода, обратные слеши не удваиваются:

Текст

Поле Reserved Strings в интерфейсе обфускатора с регулярным выражением, введённым с одинарными обратными слешами

API

JavaScript

После обфускации

JavaScript

Подстановка на стороне сервера

Поскольку плейсхолдер находится внутри JS-строки в двойных кавычках, в подставляемых JSON-данных нужно экранировать обратные слеши и внутренние символы ":

Код

Итоговый результат:

JavaScript

Во время выполнения: JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"].

Если забыть об экранировании, браузер увидит несбалансированные кавычки и выбросит SyntaxError. Паттерн 2 полностью обходит проблему двойных кавычек за счёт обратных кавычек.

Несколько плейсхолдеров в одной программе

Все три паттерна можно сочетать. Одно регулярное выражение reservedStrings с альтернацией может соответствовать всем формам плейсхолдеров, которые выводит ваш шаблонизатор, - здесь это оба стиля, {{ .Field }} и %{ .Field }:

JavaScript

В одном исходном файле можно сочетать плейсхолдеры-идентификаторы (для готовых JS-значений) и строковые плейсхолдеры (для отрисованного JSON): выбирайте вид для каждого плейсхолдера в зависимости от того, что сервер будет подставлять на самом деле.

Замечания о совместимости

vmSelfDefending ломает подстановку после обфускации

VM Self Defending обнаруживает любое изменение обфусцированного результата после сборки, включая легитимную подстановку в шаблон, и тогда защищённый код отказывается работать.

Если вы используете подстановку в шаблон на стороне сервера, задайте vmSelfDefending: false. Оставьте выключенным и selfDefending: при VM-обфускации он ни на что не влияет, а без VM тоже запрещает любые изменения результата.

Советы по плейсхолдерам

  • Не используйте один и тот же плейсхолдер и для идентификаторов, и для строк. Зарезервированное имя вроде __TOKEN__ и зарезервированная строка, соответствующая __TOKEN__, проходят двумя разными путями (массив зарезервированных выражений и массив зарезервированных строк). Используйте для них разные текстовые формы: например, соглашение UPPER_SNAKE для плейсхолдеров-идентификаторов и форму с разделителями ({{ ... }}, %{...}, <<<...>>>) для строковых плейсхолдеров. Тогда ошибка в одном регулярном выражении не сможет незаметно захватить плейсхолдеры другого вида.
  • Регулярные выражения reservedStrings применяются к самим значениям строк. Регулярное выражение сопоставляется со значением строки во время выполнения, а не с исходным текстом. \{\{[^}]+\}\} соответствует строкам, которые содержат {{.something}} (или любую другую форму {{...}}). Если плейсхолдер может быть окружён дополнительным текстом ("prefix-{{.Field}}-suffix"), регулярное выражение всё равно сработает, но сохранена будет вся строка - учитывайте это при подстановке.
  • Работает с любым шаблонизатором. Хотя в примерах используется синтаксис Go, в интеграции с javascript-obfuscator нет ничего специфичного для Go. Подойдёт всё, что умеет выполнять строковую замену в результате обфускатора: Rails ERB, Django, короткие теги PHP, sed в CI-конвейере и т. д. Выбирайте разделители, которые ваш шаблонизатор выводит естественным образом и которые не конфликтуют с настоящим синтаксисом JS.