Newcomposers.ru

IT Мир
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Javasckript eval atob

Eval: run a code string

The built-in eval function allows to execute a string of code.

A string of code may be long, contain line breaks, function declarations, variables and so on.

The result of eval is the result of the last statement.

The eval’ed code is executed in the current lexical environment, so it can see outer variables:

It can change outer variables as well:

In strict mode, eval has its own lexical environment. So functions and variables, declared inside eval, are not visible outside:

Without use strict , eval doesn’t have its own lexical environment, so we would see x and f outside.

Using “eval”

In modern programming eval is used very sparingly. It’s often said that “eval is evil”.

The reason is simple: long, long time ago JavaScript was a much weaker language, many things could only be done with eval . But that time passed a decade ago.

Right now, there’s almost no reason to use eval . If someone is using it, there’s a good chance they can replace it with a modern language construct or a JavaScript Module.

Please note that its ability to access outer variables has side-effects.

Code minifiers (tools used before JS gets to production, to compress it) rename local variables into shorter ones (like a , b etc) to make the code smaller. That’s usually safe, but not if eval is used, as local variables may be accessed from eval’ed code string. So minifiers don’t do that renaming for all variables potentially visible from eval . That negatively affects code compression ratio.

Using outer local variables inside eval is also considered a bad programming practice, as it makes maintaining the code more difficult.

There are two ways how to be totally safe from such problems.

If eval’ed code doesn’t use outer variables, please call eval as window.eval(. ) :

This way the code is executed in the global scope:

If eval’ed code needs local variables, change eval to new Function and pass them as arguments:

The new Function construct is explained in the chapter The «new Function» syntax. It creates a function from a string, also in the global scope. So it can’t see local variables. But it’s so much clearer to pass them explicitly as arguments, like in the example above.

Summary

A call to eval(code) runs the string of code and returns the result of the last statement.

  • Rarely used in modern JavaScript, as there’s usually no need.
  • Can access outer local variables. That’s considered bad practice.
  • Instead, to eval the code in the global scope, use window.eval(code) .
  • Or, if your code needs some data from the outer scope, use new Function and pass it as arguments.

Tasks

Eval-calculator

Create a calculator that prompts for an arithmetic expression and returns its result.

There’s no need to check the expression for correctness in this task. Just evaluate and return the result.

Let’s use eval to calculate the maths expression:

The user can input any text or code though.

To make things safe, and limit it to arithmetics only, we can check the expr using a regular expression, so that it only may contain digits and operators.

Comments

  • If you have suggestions what to improve — please submit a GitHub issue or a pull request instead of commenting.
  • If you can’t understand something in the article – please elaborate.
  • To insert a few words of code, use the tag, for several lines – use

, for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)

Eval: выполнение строки кода

Встроенная функция eval позволяет выполнять строку кода.

Строка кода может быть большой, содержать переводы строк, объявления функций, переменные и т.п.

Результатом eval будет результат выполнения последней инструкции.

Код в eval выполняется в текущем лексическом окружении, поэтому ему доступны внешние переменные:

Значения внешних переменных можно изменять:

В строгом режиме у eval имеется своё лексическое окружение. Поэтому функции и переменные, объявленные внутри eval , нельзя увидеть снаружи:

Без use strict у eval не будет отдельного лексического окружения, поэтому x и f будут видны из внешнего кода.

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

В современной разработке на JavaScript eval используется весьма редко. Есть даже известное выражение – «eval is evil» («eval – это зло»).

Причина такого отношения достаточно проста: давным-давно JavaScript был не очень развитым языком, и многие вещи можно было сделать только с помощью eval . Но та эпоха закончилась более десяти лет назад.

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

Пожалуйста, имейте в виду, что код в eval способен получать доступ к внешним переменным, и это может иметь побочные эффекты.

Минификаторы кода (инструменты, используемые для сжатия JS-кода перед тем, как отправить его конечным пользователям) заменяют локальные переменные на другие с более короткими именами для оптимизации. Обычно это безопасная манипуляция, но не тогда, когда в коде используется eval , так как код из eval может изменять значения локальных переменных. Поэтому минификаторы не трогают имена переменных, которые могут быть доступны из eval . Это ухудшает степень сжатия кода.

Использование внутри eval локальных переменных из внешнего кода считается плохим решением, так как это усложняет задачу по поддержке такого кода.

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

Если код внутри eval не использует внешние переменные, то вызывайте его так – window.eval(. ) :

В этом случае код выполняется в глобальной области видимости:

Если коду внутри eval нужны локальные переменные, поменяйте eval на new Function и передавайте необходимые данные как аргументы:

Конструкция new Function объясняется в главе Синтаксис «new Function». Она создаёт функцию из строки в глобальной области видимости. Так что локальные переменные для неё невидимы, но всегда можно передать их как аргументы. Получается очень аккуратный код, как в примере выше.

Итого

Вызов eval(code) выполняет строку кода и возвращает результат последней инструкции.

  • Это редко используется в современном JavaScript, так как в этом обычно нет необходимости.
  • Возможен доступ к внешним локальным переменным. Это считается плохой практикой.
  • Чтобы выполнить строку кода с помощью eval в глобальной области видимости, используйте window.eval(code) .
  • Или же, если ваш код нуждается в каких-то данных из внешней области видимости, то используйте new Function , передав эти данные в качестве аргументов.
Читать еще:  Java uninstall tool

Задачи

Eval-калькулятор

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

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

Давайте будем использовать eval для вычисления арифметических выражений:

Пользователь может ввести любой текст или код.

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

Комментарии

  • Если вам кажется, что в статье что-то не так — вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег , для нескольких строк кода — тег

, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)

  • Если что-то непонятно в статье — пишите, что именно и с какого места.
  • Прячем JavaScript-код на фронтенде от посторонних

      Блоги, 2 декабря 2018 в 14:39

    Рассказывает веб-разработчик Денис Лисогорский

    Давайте представим ситуацию, когда вы и ваша команда пишете интересный и сложный код на JavaScript. Причём этот код в кратчайшие сроки нужно использовать в проекте. Если его функциональность действительно уникальна, то в процессе разработки и у вас, и у членов команды вполне резонно возникнет вопрос: «Как защитить код от копирования?».

    Как защитить код: веб-сокеты, крипторы и обфускация

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

    Итак, есть несколько вариантов защиты кода:

    1. Использовать веб-сокеты.
    2. Использовать крипторы.
    3. Сделать обфускацию кода.

    Крипторы приводят код в нечитаемый вид, используя, как правило, base64 (что неизбежно приводит к увеличению объёма кода примерно на 30%). Затем к полученному результату прибавляется так называемая «соль» — набор символов, который при разборе кода функцией-дешифровщиком используется в качестве ключа. Ну а потом вся строка кода обычно выполняется через eval(). Проблема крипторов в том, что если понять принцип их работы, отсечь «соль» и декодировать, то сразу становится доступен весь код в его исходном виде.

    Обфускаторы же изменяют сам код, вставляя между операторами нечитаемые символы, меняя имена переменных и функций на набор визуально непонятных символов. При этом объём кода также сильно увеличивается из-за вставки дополнительного псевдокода, а также замены символов на hex, когда любые символы переводятся в их hex-значения (например, латинская буква ‘e’ может быть записана как ‘x65’, причём это прекрасно интерпретируется любым браузером). Можете посмотреть, как работает перевод в hex через любой сервис Text To Hex, например на Crypt Online.

    Применение обфускаторов сильно усложняет дальнейшую отладку кода, поскольку это необратимый процесс. К тому же в некоторых случаях они могут повлиять на функциональность кода. Попробовать обфускаторы можно на любом сервисе обфускации, к примеру этом или этом. Также в Сети можно найти платные крипторы-обфускаторы, в настройках которых вы сможете указывать степень защиты, время жизни скрипта и прочее, при этом скрипт будет намертво привязан к вашему домену, т.е. для дешифровки будет использоваться уникальное для вашего хоста значение. Стоимость таких крипторов начинается от 45 $. Кроме этого, перед обфускацией вы можете предварительно минимизировать код, заменив все имена переменных и функций на их односимвольные синонимы. Отличный и очень популярный инструмент на Node.js — UglifyJS, который работает как в автоматическом режиме (скажем, через Gulp), так и в режиме командной строки.

    Ивент перенесён, есть новые даты ( 18 – 19 июня ) , Санкт-Петербург, 10 000–138 000 ₽

    Также есть всем известный Closure Compiler от Google, который кроме минимизации анализирует JavaScript-код, удаляет мёртвый код, переписывает и сводит к минимуму то, что осталось. Он также проверяет синтаксис, ссылки на переменные и типы и предупреждает об общих ошибках JavaScript. Имеет хорошо документированный API.

    Кроме предложенных методов можно сделать следующее:

    • использовать WebStorage и скрывать там JavaScript код;
    • прятать часть кода в отдельном файле на сервере и вызывать его через XMLHttpRequest ;
    • использовать побитовые операторы для замены чисел на наборы скобок и знака

    ;

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

    Зашифровка кода на примере JavaScript-калькулятора

    Не так давно я разработал JavaScript-калькулятор для расчёта стоимости услуг, с большим количеством взаимосвязанных параметров. Руководство поставило задачу защитить данный скрипт от копирования, чтобы конкуренты не смогли использовать его на своих сайтах. Искал различные решения, ничего подходящего не нашёл, поэтому начал писать собственное. Представляю его ниже.

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

    По итогам работ в браузере вы увидите нечто такое:

    При этом все зашифрованные скрипты будут работать корректно. Опытный взгляд программиста сразу визуально определит кодирование через base64. Но при попытке расшифровать строку любым base64 декодером, будет ошибка. Если вставить скрипт в alert (такой метод также рекомендуют на форумах для дешифровки кода), то результат также будет нулевым.

    При этом никто ведь не знает, что здесь зашифрован именно скрипт. Это может оказаться какой-то параметр, текст или изображение. Через base64 можно зашифровать всё что угодно.

    Поищем в коде функцию glob() , которой передаётся шифрованная строка. Вот она: glob=function(s)

    Видим ещё несколько функций sfd() и rty() . Ищем эти функции. Вот они:

    На этом месте многие закончат попытки расшифровки и оставят ваш сайт в покое.

    Рассмотрим алгоритм подробнее.

    Как защитить JavaScript от копирования на своём сайте

    Первым делом указываем в футере сайта путь на скрипт и тут же кодируем его:

    В строке выше мы говорим интерпретатору PHP взять файл script.js, далее закодировать его через base64, прибавить строку ‘K’ и всё это записать в переменную $filebase64 .

    Добавление строки ‘K’ (это может быть любая латинская буква или комбинация букв или цифр) защищает нас от того, что желающий скопировать ваш скрипт расшифрует его с помощью alert() или онлайн-дешифратора. Ведь с дополнительными символами скрипт не будет работоспособен.

    Затем где-то дальше в коде вызываем скрипт:

    Пусть этот скрипт вызывается отдельно, подальше от других скриптов и ссылок на скрипты.

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

    Разбираем подробно что здесь происходит.

    Наша основная функция glob() принимает один параметр s . Он сразу передаётся функции substring() с параметром —

    [] (это равно 1 в зашифрованном виде), которая извлекает из s строку начиная с первого символа и до конца. Следовательно, если мы в PHP-коде в качестве строки прибавляли более одного символа, скажем три, то нам нужно будет в функции substring() указать 2+(-

    []) . Либо путём шифрования цифр через побитовые операторы мы можем создать запутанную формулу, часть переменных которой мы можем прятать в cookies или sessionStorage, что сделает крайне затруднительным понимание того, какое количество символов необходимо отбросить для дешифровки кода.

    Пример замены цифр через побитовый оператор

      -1 можно заменить на

    []
    1 можно заменить на —

    []
    0 можно заменить на

    Далее полученный результат принимает функция rty() . Эта функция представляет собой набор символов, в частности: this[«x61x74x6Fx62»] ;

    Попробуйте ввести это в консоли браузера и вы увидите, что на самом деле делает эта функция. Например, вы увидите:

    Т.е. набор символов — это зашифрованная функция atob() , которая, согласно описанию на MDN, декодирует строку, закодированную с использованием base64.

    Результат декодирования получает функция sfd() . Она также представляет собой набор символов: this[«x65x76x61x6C»]; .

    Вы уже догадались, что нужно сделать? Выполните в консоли браузера и вы увидите:

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

    Наверное, вы задались вопросом, а каким же образом функции зашифрованы в наборе символов? Очень просто: набор символов — это текст, преобразованный в шестнадцатеричную систему счисления. Т.е. это текст в формате hex (hexadecimal), в котором можно зашифровать любые символы.

    Таким образом, наша расшифрованная функция выглядит так (специально разбил по строчкам, чтобы было наглядно):

    В итоге отбрасываем первый символ шифрованной строки (при этом символов может быть хоть 353, и об этом никто не сможет быстро догадаться), потом дешифруем, потом выполняем через eval() .

    Вы можете пойти и дальше. Если каким-то образом кто-то всё же расшифрует ваш скрипт, немного усложните его, чтобы людям было сложнее модифицировать его. Например, можно поговорить о побитовом операторе ^ , c помощью которого можно творить чудеса. Например, a^b^b будет равно a . В качестве b может быть использован ключ, который мы зашифруем где-то выше…

    Всё будет работать как и раньше, но собьёт с толку нехороших копипастеров.

    За материал благодарим нашего подписчика Дениса Лисогорского

    • Главная &nbsp / &nbspУроки &nbsp / &nbspУроки JavaScript &nbsp / &nbspГлава 2. Основы JavaScript &nbsp / &nbsp
    • Запуск кода из строки: метод eval

    Запуск кода из строки: метод eval

    Здравствуйте! В этом уроке речь пойдет о таком может малоизвестном методе eval. Функция eval(code) позволяет выполнить любой код, переданный ей в виде строки. И в этом кроется ее опасность ведь если ей передать зловредный код в виде строки, то он будет выполнен.

    Этот код будет выполнен в текущей области видимости.

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

    В простейшем случае eval всего лишь выполняет код, вот пример:

    Но он может не только выполнить код, но и также вернуть результат.
    Вызов eval возвращает последнее вычисленное выражение, например:

    При вызове eval имеет полный доступ к локальным переменным.

    Это означает, что переменные могут быть изменены или дополнены:

    В строгом режиме eval имеет свою область видимости

    В строгом режиме функционал eval чуть-чуть меняется.
    При use strict код внутри eval по-прежнему сможет читать и менять внешние переменные, однако переменные и функции, объявленные внутри eval, не попадут наружу.

    Другими словами, в новом стандарте eval имеет свою область видимости, а к внешним переменным обращается через замыкание, аналогично тому, как работают обычные функции.

    Неграмотное использование eval

    Начнём с того, что eval применяется довольно очень редко. Действительно редко. Есть даже такое выражение как «eval is evil» (eval – зло).

    Причина проста: когда-то JavaScript был гораздо более слабым языком, чем сейчас, и некоторые вещи без eval было попросту сделать невозможно. Но те времена давно канули в лету. И теперь найти тот случай, когда действительно надо выполнить код из строки – это надо сильно постараться.

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

    Доступ к локальным переменным – худшее, что можно сделать при eval.

    Дело в том, что локальные переменные могут быть очень легко переименованы:

    Переменная phrase может быть переименована в hello, и если строка str обращается к ней – будет ошибка.

    Современные средства сжатия JavaScript переименовывают локальные переменные автоматически. Это считается безопасным, так как локальная переменная видна лишь внутри функции и если в ней везде поменять phrase на p, то никто этого и не заметит.
    До сжатия:

    На самом деле всё ещё проще – в данном случае утилита сжатия автоматически уберёт переменную a и код станет таким:

    Итак, если где-то в функции есть eval, то его взаимодействие с локальными переменными будет нарушено.

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

    Как правило, eval не нужен, именно поэтому и говорят: «eval is evil».

    Запуск скрипта в глобальной области

    Хорошо взаимодействовать с локальными переменными нельзя.

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

    Здесь eval может пригодиться. Имеются 2 трюка для выполнения кода в глобальной области:

    1. Везде, кроме IE8-, достаточно вызвать eval не напрямую, а через window.eval.Вот так:
    1. В IE8- можно применить нестандартную фунцию execScript. Она, как и собственно eval, выполняет код, но всегда только в глобальной области видимости и не возвращает значение.

    Оба способа можно объединить в единой функции globalEval(code), выполняющей код без доступа к локальным переменным:

    Внешние данные через new Function

    Итак, у нас есть код, который всё же нужно выполнить динамически, через eval, но не просто скрипт – а ему нужно передать какие-то значения.

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

    К счастью, существует отличная альтернатива eval, которая позволяет корректно взаимодействовать с внешним кодом: new Function.

    Вызов new Function(‘a,b’, ‘..тело..’) создает функцию с указанными аргументами a,b и телом. Как мы помним, доступа к текущему замыканию у такой функции не будет, но вы можете передать параметры и получить результат.

    JSON и eval

    В браузерах IE7- не было методов JSON.stringify и JSON.parse, поэтому работа с JSON происходила через eval.

    Этот способ работы с JSON давно устарел, но его можно встретить кое-где в старом коде, так что для примера рассмотрим его.

    Вызов eval(code) выполняет код и, если это выражение, то возвращает его значение, поэтому можно в качестве кода передать JSON.

    Зачем здесь нужны скобки eval( ‘(‘ + str + ‘)’ ), почему не просто eval(str)?

    Поэтому если передать в eval объект напрямую, то интерпретатор подумает, что это на самом деле блок кода, а там внутри какие-то двоеточия

    Вот, для примера, eval без скобок, он выдаст ошибку:

    А если eval получает выражение в скобках ( … ), то интерпретатор точно знает, что это не блок кода, а объект:

    Осторожно, злой JSON!

    Если мы получаем JSON из недоверенного источника, например с чужого сервера, то разбор через eval может быть и опасен.

    Например, чужой сервер может быть взломан (за свой-то код мы отвечаем, а вот за чужой – нет), и вместо JSON вставлен злонамеренный JavaScript-код.

    Поэтому рекомендуется, всё же, использовать JSON.parse.

    При разборе через JSON.parse некорректный JSON просто приведёт к ошибке, а вот при разборе через eval этот код реально выполнится, и он может вывести что-то на странице, перенаправить посетителя куда-то и т.п да мало ли что еще можно придумать.

    Итоги

    • Функция eval(str) выполняет код и возвращает последнее вычисленное выражение. В современном JavaScript она используется очень редко.
    • Вызов eval может читать и менять локальные переменные. Это – зло, поэтому следует этого избегать.
    • Для выполнения скрипта в глобальной области используются трюк с window.eval/execScript. При этом локальные переменные не будут затронуты, так что такое выполнение безопасно и иногда, в редких архитектурах, может быть полезным.
    • Если выполняемый код всё же должен взаимодействовать с локальными переменными – используйте выражение new Function. Создавайте функцию из строки и передавайте переменные ей, это надёжно и безопасно.

    Задания

    Eval-калькулятор

    Вам надо написать интерфейс, который принимает математическое выражение (prompt) и возвращает его результат.

    Проверять выражение на корректность не требуется.

    Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

    Eval: run a code string

    The built-in eval function allows to execute a string of code.

    A string of code may be long, contain line breaks, function declarations, variables and so on.

    The result of eval is the result of the last statement.

    The eval’ed code is executed in the current lexical environment, so it can see outer variables:

    It can change outer variables as well:

    In strict mode, eval has its own lexical environment. So functions and variables, declared inside eval, are not visible outside:

    Without use strict , eval doesn’t have its own lexical environment, so we would see x and f outside.

    Using “eval”

    In modern programming eval is used very sparingly. It’s often said that “eval is evil”.

    The reason is simple: long, long time ago JavaScript was a much weaker language, many things could only be done with eval . But that time passed a decade ago.

    Right now, there’s almost no reason to use eval . If someone is using it, there’s a good chance they can replace it with a modern language construct or a JavaScript Module.

    Please note that its ability to access outer variables has side-effects.

    Code minifiers (tools used before JS gets to production, to compress it) rename local variables into shorter ones (like a , b etc) to make the code smaller. That’s usually safe, but not if eval is used, as local variables may be accessed from eval’ed code string. So minifiers don’t do that renaming for all variables potentially visible from eval . That negatively affects code compression ratio.

    Using outer local variables inside eval is also considered a bad programming practice, as it makes maintaining the code more difficult.

    There are two ways how to be totally safe from such problems.

    If eval’ed code doesn’t use outer variables, please call eval as window.eval(. ) :

    This way the code is executed in the global scope:

    If eval’ed code needs local variables, change eval to new Function and pass them as arguments:

    The new Function construct is explained in the chapter The «new Function» syntax. It creates a function from a string, also in the global scope. So it can’t see local variables. But it’s so much clearer to pass them explicitly as arguments, like in the example above.

    Summary

    A call to eval(code) runs the string of code and returns the result of the last statement.

    • Rarely used in modern JavaScript, as there’s usually no need.
    • Can access outer local variables. That’s considered bad practice.
    • Instead, to eval the code in the global scope, use window.eval(code) .
    • Or, if your code needs some data from the outer scope, use new Function and pass it as arguments.

    Tasks

    Eval-calculator

    Create a calculator that prompts for an arithmetic expression and returns its result.

    There’s no need to check the expression for correctness in this task. Just evaluate and return the result.

    Let’s use eval to calculate the maths expression:

    The user can input any text or code though.

    To make things safe, and limit it to arithmetics only, we can check the expr using a regular expression, so that it only may contain digits and operators.

    Comments

    • If you have suggestions what to improve — please submit a GitHub issue or a pull request instead of commenting.
    • If you can’t understand something in the article – please elaborate.
    • To insert a few words of code, use the tag, for several lines – use

    , for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)

    Ссылка на основную публикацию
    Adblock
    detector