JSLint "eval - зло". альтернативы

У меня есть некоторые функции JavaScript, которые выполняются как на клиенте (браузере), так и на сервере (в контексте Java Rhino). Это небольшие функции - в основном мало валидаторы, которые хорошо определены и не зависят от глобальных или закрытых - автономных и портативных.

Вот пример:

function validPhoneFormat(fullObject, value, params, property) {
    var phonePattern = /^\+?([0-9\- \(\)])*$/;
    if (value && value.length && !phonePattern.test(value))
        return [ {"policyRequirement": "VALID_PHONE_FORMAT"}];
    else
        return [];
}

Чтобы сохранить вещи DRY, мой код сервера получает дескриптор каждой из этих функций и вызывает toString() на них, возвращая их в браузер как часть объекта JSON. Что-то вроде этого:

      { "name" : "phoneNumber",
        "policies" : [ 
            { "policyFunction" : "\nfunction validPhoneFormat(fullObject, value, params, property) {\n    var phonePattern = /^\\+?([0-9\\- \\(\\)])*$/;\n    if (value && value.length && !phonePattern.test(value)) {\n        return [{\"policyRequirement\":\"VALID_PHONE_FORMAT\"}];\n    } else {\n        return [];\n    }\n}\n"
            }
          ]
      }

Мой браузер JS-код затем берет этот ответ и создает экземпляр этой функции в этом контексте, например:

eval("var policyFunction = " + this.policies[j].policyFunction);

policyFailures = policyFunction.call(this, form2js(this.input.closest("form")[0]), this.input.val(), params, this.property.name));

Все это работает очень хорошо. Однако я запускаю этот код через JSLint, и я возвращаю это сообщение:

[ERROR] ValidatorsManager.js: 142: 37: eval - это зло.

Я ценю, что часто eval может быть опасным. Однако я понятия не имею, как еще я мог реализовать такой механизм, не используя его. Есть ли способ, которым я могу это сделать, а также пройти проверку достоверности JSLint?

Ответ 1

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

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

Update:

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

var policyFunction = YourLibraryName[this.policies[j].policyFunctionName];
var policyArguments = this.policies[j].policyArguments;

policyFunction.apply(this, policyArguments); 

Обновление 2:

Я смог успешно проверить следующий код с JSLint, что существенно позволяет вам "отключить" проверку для огромного числа случаев, когда eval подходит. В то же время JSLint все еще проверяет обычные вызовы eval, и все применения этого метода должны вызывать флаги для будущих разработчиков, чтобы избежать использования/рефакторинга, где это возможно/по мере возможности.

var EVAL_IS_BAD__AVOID_THIS = eval;
EVAL_IS_BAD__AVOID_THIS(<yourString>);

Ответ 2

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

Если это не выполнимо, вы также можете создать динамически сгенерированный файл javascript, а затем передать необходимые параметры с помощью ответа, а затем динамически загрузить script на стороне клиента. Там действительно нет причин использовать eval.

Надеюсь, что это поможет.

Ответ 3

Не кодируйте функцию как строку в JSON. JSON предназначен для контента, который вы смешиваете с поведением.

Вместо этого, я полагаю, вы могли бы вернуть JS файлы, которые позволяют выполнять реальные функции:

 { name : "phoneNumber",
    policies : [ 
        { policyFunction : function() {
              whateverYouNeed('here');
          }
        }
      ]
  }

Но пока это решает техническую проблему, это все еще не очень хорошая идея.


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

Получение ваших данных, запутанных с вашим кодом, обычно приводит к боли. Вы должны статически включать JS, затем динамически запрашивать/импортировать/запрашивать данные JSON для запуска вашего статически включенного кода.

Ответ 4

С очень небольшим разбором у вас могло получиться так:

var body = this.policies[j].policyFunction.substr;
body = body.substr(body.indexOf("(") + 1);
var arglist = body.substr(1, body.indexOf(")"));
body = body.substr(arglist.length + 1);
var policyFunction = new Function(arglist, body);

Это обеспечило бы небольшую проверку, избегало бы буквального использования eval и работало синхронно с кодом. Но, несомненно, eval скрывается, и он подвержен атаке XSS. Если злонамеренный человек может загружать и оценивать свой код таким образом, он не спасет вас. Так что, действительно, просто не делай этого. Добавьте тег <script> с правильным URL-адресом, и это будет, безусловно, безопаснее. Ну, вы знаете, лучше безопасно, чем жаль.

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

Ответ 5

С DRY я определенно согласен, но есть момент, когда копирование + вставка более эффективны и просты в обслуживании, чем ссылки на один и тот же фрагмент кода.

Код, который вы спасаете от написания, кажется, эквивалентен чистому интерфейсу и простой платформе. Если один и тот же код используется как на сервере, так и на клиенте, вы можете просто передать общие части функции, а не всю функцию.

Payload:
{
    "name": "phoneNumber",
    "type": "regexCheck",
    "checkData": "/^\\+?([0-9\\- \\(\\)])*$/"
}
if(payload.type === "regexCheck"){
    const result = validPhoneFormat(fullObject, value, payload.checkData)
}

function validPhoneFormat(fullObject, value, regexPattern) {

    if (value && value.length && !regexPattern.test(value))
        return [ {"policyRequirement": "VALID_PHONE_FORMAT"}];
    else
        return [];
}

Это даст вам возможность обновлять регулярные выражения из одного места. Если интерфейс меняется, его нужно обновить в 2 местах, но я бы не посчитал это плохим. Если на клиенте выполняется код, зачем скрывать структуру?

Если вы действительно хотите сохранить структуру объекта и шаблоны в одном месте - извлеките их в один API. Имейте конечную точку API ValidatePhoneViaRegex, которая вызывается всеми местами, куда вы будете передавать эту сериализованную функцию.

Если все это кажется слишком большим усилием, установите jslint, чтобы игнорировать ваш кусок кода:

"В JSHint 1.0.0 и выше у вас есть возможность игнорировать любое предупреждение со специальным синтаксисом параметра. Идентификатор этого предупреждения - W061. Это означает, что вы можете указать JSHint не выдавать это предупреждение с помощью /* jshint -W061 */директива.

В ESLint правило, генерирующее это предупреждение, называется no-eval. Вы можете отключить его, установив его на 0, или включить его, установив на 1. "

https://github.com/jamesallardice/jslint-error-explanations/blob/master/message-articles/eval.md

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

Что произойдет, если вы получите ошибку совместимости браузера с одной из этих общих функций?

Ответ 6

Вы можете использовать

setInterval("code to be evaluated", 0);

Внутренне, если вы передаете setInterval строку, она выполняет функцию, похожую на eval().

Однако я бы не стал беспокоиться об этом. Если вы ЗНАЕТ eval() является злом и принимаете соответствующие меры предосторожности, это не проблема. Eval похож на GoTo; вам просто нужно быть осторожным и осознавать, что вы делаете, чтобы правильно их использовать.

Ответ 7

Ну, первое, что нужно иметь в виду, это то, что jsLint делает вывод, что "это повредит вашим чувствам". Он предназначен, чтобы указать, где вы не следуете лучшим практикам - но код, который не идеален, может все еще работать нормально; нет никакого принуждения к вам следовать совету jsLint.

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

В этом случае вы можете использовать библиотеку, такую ​​как require.js, yepnope.js или другую библиотеку, предназначенную для загрузки script отдельно. Это позволит вам включать функции javascript, которые вам нужны динамически, но без eval() их.

Есть, вероятно, еще несколько других решений, но это было первым, что пришло мне в голову.

Надеюсь, что это поможет.