]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3415: http_inspect: add ignoring defined object properties for Enchance...
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Wed, 11 May 2022 13:53:47 +0000 (13:53 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Wed, 11 May 2022 13:53:47 +0000 (13:53 +0000)
Merge in SNORT/snort3 from ~VHORBATO/snort3:js_prop_ignore to master

Squashed commit of the following:

commit eb135f3e3ccfec12f622f9d2770a5f2175a0fc52
Author: Vitalii <vhorbato@cisco.com>
Date:   Fri Apr 29 12:51:52 2022 +0300

    http_inspect: add ignoring defined object properties for Enchanced JS normalizer

19 files changed:
doc/user/http_inspect.txt
lua/snort_defaults.lua
src/service_inspectors/http_inspect/dev_notes.txt
src/service_inspectors/http_inspect/http_flow_data.cc
src/service_inspectors/http_inspect/http_flow_data.h
src/service_inspectors/http_inspect/http_inspect.cc
src/service_inspectors/http_inspect/http_js_norm.cc
src/service_inspectors/http_inspect/http_js_norm.h
src/service_inspectors/http_inspect/http_module.cc
src/service_inspectors/http_inspect/http_module.h
src/service_inspectors/http_inspect/test/http_module_test.cc
src/service_inspectors/http_inspect/test/http_uri_norm_test.cc
src/utils/js_identifier_ctx.cc
src/utils/js_identifier_ctx.h
src/utils/js_tokenizer.l
src/utils/test/js_identifier_ctx_test.cc
src/utils/test/js_normalizer_test.cc
src/utils/test/js_test_utils.cc
src/utils/test/js_test_utils.h

index cf7dcaf2eb5f3c7cbc1e1ccc55aa07ad7b0cc7bc..3637f5c0d51f942ef99f7ac76de69a85d8adf2d9 100755 (executable)
@@ -79,7 +79,7 @@ It supports scripts over multiple PDUs. It is a stateful JavaScript whitespace
 and identifiers normalizer. Normalizer concatenates string literals whenever 
 it's possible to do. This also works with any other normalizations that result
 in string literals. All JavaScript identifier names, except those from
-the ignore list, will be substituted with unified names in the following
+the ignore lists, will be substituted with unified names in the following
 format: var_0000 -> var_ffff. But the unescape-like function names will be removed
 from the normalized data. The Normalizer tries to expand an escaped text,
 so it will appear in a usual form in the output. Moreover, Normalizer validates
@@ -87,7 +87,8 @@ the syntax concerning ECMA-262 Standard, including scope tracking and restrictio
 for script elements. For more information on how additionally configure
 Enhanced Normalizer check with the following configuration options:
 js_norm_bytes_depth, js_norm_identifier_depth, js_norm_max_tmpl_nest,
-js_norm_max_bracket_depth, js_norm_max_scope_depth, js_norm_ident_ignore.
+js_norm_max_bracket_depth, js_norm_max_scope_depth, js_norm_ident_ignore,
+js_norm_prop_ignore.
 Eventually Enhanced Normalizer will completely replace Legacy Normalizer.
 
 ==== Configuration
@@ -284,6 +285,26 @@ Unescape function names should remain intact in the output. They ought to be
 included in the ignore list. If for some reason the user wants to disable unescape
 related features, then removing function's name from the ignore list does the trick.
 
+===== js_norm_prop_ignore
+
+js_norm_prop_ignore = {<list of ignored properties>} is an option of the enhanced
+JavaScript normalizer that defines a list of object properties and methods that
+will be kept intact during the identifiers normalization. This list should include
+methods and properties of objects that will not be tracked by assignment substitution
+functionality, for example, those that can be created implicitly.
+
+Subsequent accessors, after dot, in square brackets or after function call, will not be
+normalized as well.
+
+For example:
+
+    http_inspect.js_norm_prop_ignore = { 'split' }
+
+    in: "string".toUpperCase().split("").reverse().join("");
+    out: "string".var_0000().split("").reverse().join("");
+
+The default list of ignored properties is present in "snort_defaults.lua".
+
 ===== xff_headers
 
 This configuration supports defining custom x-forwarded-for type headers. In a
index 76ec107d63a41ebfb80710099491848c8c1d3dd5..43c1037ec9a01fa6a3fc4e777fc51c0ca6e99371 100644 (file)
@@ -1278,10 +1278,47 @@ default_js_norm_ident_ignore =
     'CreateHTML'
 }
 
+default_js_norm_prop_ignore =
+{
+    -- Object 
+    'constructor', 'prototype', '__proto__', '__defineGetter__', '__defineSetter__',
+    '__lookupGetter__', '__lookupSetter__', '__count__', '__noSuchMethod__', '__parent__',
+    'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString',
+    'toSource', 'valueOf', 'getNotifier', 'eval', 'observe', 'unobserve', 'watch', 'unwatch',
+
+    -- Function
+    'arguments', 'arity', 'caller', 'length', 'name', 'displayName', 'apply', 'bind', 'call',
+    'isGenerator',
+
+    -- Number
+    'toExponential', 'toFixed', 'toPrecision',
+
+    -- String
+    'at', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'includes', 'endWith', 'indexOf',
+    'lastIndexOf', 'localeCompare', 'match', 'matchAll', 'normalize', 'padEnd', 'padStart',
+    'repeat', 'replace', 'replaceAll', 'search', 'slice', 'split', 'startsWith', 'substring',
+    'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim', 'trimStart',
+    'trimEnd',
+
+    -- RegExp
+    'flags', 'dotAll', 'global', 'hasIndices', 'ignoreCase', 'multiline', 'source', 'sticky',
+    'unicode', 'lastIndex', 'compile', 'exec', 'test', 'input', 'lastMatch', 'lastParen',
+    'leftContext', 'rightContext',
+
+    -- Array
+    'copyWithin', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap',
+    'forEach', 'groupBy', 'groupByToMap', 'join', 'keys', 'map', 'pop',  'push', 'reduce', 
+    'reduceRight', 'reverse', 'shift', 'unshift', 'some', 'sort', 'splice',
+
+    -- Generator
+    'next', 'return', 'throw'
+}
+
 default_http_inspect =
 {
     -- params not specified here get internal defaults
     js_norm_ident_ignore = default_js_norm_ident_ignore,
+    js_norm_prop_ignore = default_js_norm_prop_ignore,
 }
 
 ---------------------------------------------------------------------------
@@ -1303,7 +1340,7 @@ default_whitelist =
     ip_hi_dist icmp_low_sweep icmp_med_sweep icmp_hi_sweep
     default_hi_port_scan default_med_port_scan default_low_port_scan
     default_variables netflow_versions default_js_norm_ident_ignore
-    default_http_inspect
+    default_js_norm_prop_ignore default_http_inspect
 ]]
 
 snort_whitelist_append(default_whitelist)
index 997d4afb502cd4660733aa133de97951c8804310..95fe7dd2e53a5eb1b7de6a83838de30c1ce51173 100755 (executable)
@@ -227,14 +227,14 @@ be kept intact. Any string literals, added by the plus operator,
 will be concatenated. This also works for functions that result in string
 literals. Semicolons will be inserted, if not already present, according to ECMAScript
 automatic semicolon insertion rules.
-All JavaScript identifier names, except those from the ignore list,
+All JavaScript identifier names, except those from the ident_ignore or prop_ignore lists,
 will be substituted with unified names in the following format: var_0000 -> var_ffff.
 So, the number of unique identifiers available is 65536 names per HTTP transaction.
 If Normalizer overruns the configured limit, built-in alert is generated.
 A config option to set the limit manually:
  * http_inspect.js_norm_identifier_depth.
 
-Identifiers from the ignore list will be placed as is, without substitution. Starting with 
+Identifiers from the ident_ignore list will be placed as is, without substitution. Starting with 
 the listed identifier, any chain of dot accessors, brackets and function calls will be kept
 intact.
 For example:
@@ -257,6 +257,21 @@ For example:
     var a = console.log
     a("hello") // will be substituted to 'console.log("hello")'
 
+For properties and methods of objects that can be created implicitly, there is a
+js_norm_prop_ignore list. All names in the call chain after the first property or
+method from the list has been occurred will not be normalized.
+
+Note that identifiers are normalized by name, i.e. an identifier and a property with the same name
+will be normalized to the same value. However, the ignore lists act separately on identifiers
+and properties.
+
+For example:
+
+   http_inspect.js_norm_prop_ignore = { 'split' }
+
+   in: "string".toUpperCase().split("").reverse().join("");
+   out: "string".var_0000().split("").reverse().join("");
+
 In addition to the scope tracking, JS Normalizer specifically tracks unicode unescape
 functions(unescape, decodeURI, decodeURIComponent, String.fromCharCode, String.fromCodePoint).
 This allows detection of unescape functions nested within other unescape functions, which is
index b5542fc8d8bee9b43eb2b0c367948e476629ba0f..c912067afb49f9a36bdd7b1c8201f286a696ec6d 100644 (file)
@@ -240,27 +240,26 @@ void HttpFlowData::reset_js_ident_ctx()
     }
 }
 
-snort::JSNormalizer& HttpFlowData::acquire_js_ctx(int32_t ident_depth, size_t norm_depth,
-    uint8_t max_template_nesting, uint32_t max_bracket_depth, uint32_t max_scope_depth,
-    const std::unordered_set<std::string>& ignored_ids)
+snort::JSNormalizer& HttpFlowData::acquire_js_ctx(const HttpParaList::JsNormParam& js_norm_param)
 {
     if (js_normalizer)
         return *js_normalizer;
 
     if (!js_ident_ctx)
     {
-        js_ident_ctx = new JSIdentifierCtx(ident_depth, max_scope_depth, ignored_ids);
+        js_ident_ctx = new JSIdentifierCtx(js_norm_param.js_identifier_depth, 
+            js_norm_param.max_scope_depth, js_norm_param.ignored_ids, js_norm_param.ignored_props);
 
         debug_logf(4, http_trace, TRACE_JS_PROC, nullptr,
-            "js_ident_ctx created (ident_depth %d)\n", ident_depth);
+            "js_ident_ctx created (ident_depth %d)\n", js_norm_param.js_identifier_depth);
     }
 
-    js_normalizer = new JSNormalizer(*js_ident_ctx, norm_depth,
-        max_template_nesting, max_bracket_depth);
+    js_normalizer = new JSNormalizer(*js_ident_ctx, js_norm_param.js_norm_bytes_depth,
+        js_norm_param.max_template_nesting, js_norm_param.max_bracket_depth);
 
     debug_logf(4, http_trace, TRACE_JS_PROC, nullptr,
         "js_normalizer created (norm_depth %zd, max_template_nesting %d)\n",
-        norm_depth, max_template_nesting);
+        js_norm_param.js_norm_bytes_depth, js_norm_param.max_template_nesting);
 
     return *js_normalizer;
 }
@@ -287,8 +286,7 @@ void HttpFlowData::release_js_ctx()
 }
 #else
 void HttpFlowData::reset_js_ident_ctx() {}
-snort::JSNormalizer& HttpFlowData::acquire_js_ctx(int32_t, size_t, uint8_t, uint32_t, uint32_t,
-    const std::unordered_set<std::string>&)
+snort::JSNormalizer& HttpFlowData::acquire_js_ctx(const HttpParaList::JsNormParam&)
 { return *js_normalizer; }
 void HttpFlowData::release_js_ctx() {}
 #endif
index 10438f2eb7d5f41e65e846c259a7102a61d49730..685b3074101b21f4d82791e130d1aa1a5d8aaba6 100644 (file)
@@ -32,6 +32,7 @@
 #include "http_common.h"
 #include "http_enum.h"
 #include "http_event.h"
+#include "http_module.h"
 
 class HttpTransaction;
 class HttpJsNorm;
@@ -211,9 +212,7 @@ private:
 
     void reset_js_pdu_idx();
     void reset_js_ident_ctx();
-    snort::JSNormalizer& acquire_js_ctx(int32_t ident_depth, size_t norm_depth,
-        uint8_t max_template_nesting, uint32_t max_bracket_depth, uint32_t max_scope_depth,
-        const std::unordered_set<std::string>& ignored_ids);
+    snort::JSNormalizer& acquire_js_ctx(const HttpParaList::JsNormParam& js_norm_param);
     void release_js_ctx();
     bool is_pdu_missed();
 
index 57edbea660fc791d66816b0a919924e72ca3147d..e19b277344dd69f2fe3e0b0a5884923e1d6914e9 100755 (executable)
@@ -159,6 +159,10 @@ void HttpInspect::show(const SnortConfig*) const
     for (auto s : params->js_norm_param.ignored_ids)
         js_norm_ident_ignore += s + " ";
 
+    std::string js_norm_prop_ignore;
+    for (auto s : params->js_norm_param.ignored_props)
+        js_norm_prop_ignore += s + " ";
+
     ConfigLogger::log_limit("request_depth", params->request_depth, -1LL);
     ConfigLogger::log_limit("response_depth", params->response_depth, -1LL);
     ConfigLogger::log_flag("unzip", params->unzip);
@@ -178,6 +182,8 @@ void HttpInspect::show(const SnortConfig*) const
     ConfigLogger::log_value("js_norm_max_scope_depth", params->js_norm_param.max_scope_depth);
     if (!js_norm_ident_ignore.empty())
         ConfigLogger::log_list("js_norm_ident_ignore", js_norm_ident_ignore.c_str());
+    if (!js_norm_prop_ignore.empty())
+        ConfigLogger::log_list("js_norm_prop_ignore", js_norm_prop_ignore.c_str());
     ConfigLogger::log_value("bad_characters", bad_chars.c_str());
     ConfigLogger::log_value("ignore_unreserved", unreserved_chars.c_str());
     ConfigLogger::log_flag("percent_u", params->uri_param.percent_u);
index 0964132521f0d7011da117bf6acb1ee3e9e891a8..8a6a671618ef8478d5a6274157fd4bc561b2e32a 100644 (file)
@@ -80,17 +80,11 @@ static inline JSTokenizer::JSRet js_normalize(JSNormalizer& ctx, const Packet* c
     return ret;
 }
 
-HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_,
-    int32_t identifier_depth_, uint8_t max_template_nesting_, uint32_t max_bracket_depth_,
-    uint32_t max_scope_depth_, const std::unordered_set<std::string>& ignored_ids_) :
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_,
+    const HttpParaList::JsNormParam& js_norm_param_) :
     uri_param(uri_param_),
+    js_norm_param(js_norm_param_),
     detection_depth(UINT64_MAX),
-    normalization_depth(normalization_depth_),
-    identifier_depth(identifier_depth_),
-    max_template_nesting(max_template_nesting_),
-    max_bracket_depth(max_bracket_depth_),
-    max_scope_depth(max_scope_depth_),
-    ignored_ids(ignored_ids_),
     mpse_otag(nullptr),
     mpse_attr(nullptr),
     mpse_type(nullptr)
@@ -159,8 +153,7 @@ void HttpJsNorm::do_external(const Field& input, Field& output,
         trace_logf(2, http_trace, TRACE_JS_PROC, current_packet,
             "script continues\n");
 
-    auto& js_ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth, max_template_nesting,
-        max_bracket_depth, max_scope_depth, ignored_ids);
+    auto& js_ctx = ssn->acquire_js_ctx(js_norm_param);
 
     while (ptr < end)
     {
@@ -306,8 +299,7 @@ void HttpJsNorm::do_inline(const Field& input, Field& output,
                 HttpModule::increment_peg_counts(PEG_JS_INLINE);
         }
 
-        auto& js_ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth,
-            max_template_nesting, max_bracket_depth, max_scope_depth, ignored_ids);
+        auto& js_ctx = ssn->acquire_js_ctx(js_norm_param);
         auto output_size_before = js_ctx.script_size();
 
         auto ret = js_normalize(js_ctx, current_packet, end, ptr, false);
index 891a2dd89489092294bed446e4b32adf771db35c..e6261fd9d4796e61f7f0a02814df2439be92a243 100644 (file)
@@ -36,9 +36,8 @@
 class HttpJsNorm
 {
 public:
-    HttpJsNorm(const HttpParaList::UriParam&, int64_t normalization_depth,
-        int32_t identifier_depth, uint8_t max_template_nesting, uint32_t max_bracket_depth,
-        uint32_t max_scope_depth, const std::unordered_set<std::string>& ignored_ids);
+    HttpJsNorm(const HttpParaList::UriParam& uri_param_,
+        const HttpParaList::JsNormParam& js_norm_param_);
     ~HttpJsNorm();
 
     void set_detection_depth(size_t depth)
@@ -63,13 +62,8 @@ private:
     };
 
     const HttpParaList::UriParam& uri_param;
+    const HttpParaList::JsNormParam& js_norm_param;
     size_t detection_depth;
-    int64_t normalization_depth;
-    int32_t identifier_depth;
-    uint8_t max_template_nesting;
-    uint32_t max_bracket_depth;
-    uint32_t max_scope_depth;
-    const std::unordered_set<std::string>& ignored_ids;
     bool configure_once = false;
 
     snort::SearchTool* mpse_otag;
index 6c224dfe9900cc2368682f3e720f389002801c72..c7451e90609c983b4d97d3012aaf8ec0f61c1b6f 100755 (executable)
@@ -51,6 +51,12 @@ static const Parameter js_norm_ident_ignore_param[] =
     { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
 };
 
+static const Parameter js_norm_prop_ignore_param[] =
+{
+    { "prop_name", Parameter::PT_STRING, nullptr, nullptr, "name of the object property to ignore" },
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
 const Parameter HttpModule::http_params[] =
 {
     { "request_depth", Parameter::PT_INT, "-1:max53", "-1",
@@ -109,6 +115,9 @@ const Parameter HttpModule::http_params[] =
     { "js_norm_ident_ignore", Parameter::PT_LIST, js_norm_ident_ignore_param, nullptr,
       "list of JavaScript ignored identifiers which will not be normalized" },
 
+    { "js_norm_prop_ignore", Parameter::PT_LIST, js_norm_prop_ignore_param, nullptr,
+      "list of JavaScript ignored object properties which will not be normalized" },
+
     { "max_javascript_whitespaces", Parameter::PT_INT, "1:65535", "200",
       "maximum consecutive whitespaces allowed within the JavaScript obfuscated data" },
 
@@ -293,6 +302,10 @@ bool HttpModule::set(const char*, Value& val, SnortConfig*)
     {
         params->js_norm_param.ignored_ids.insert(val.get_string());
     }
+    else if (val.is("prop_name"))
+    {
+        params->js_norm_param.ignored_props.insert(val.get_string());
+    }
     else if (val.is("max_javascript_whitespaces"))
     {
         params->js_norm_param.max_javascript_whitespaces = val.get_uint16();
@@ -474,10 +487,7 @@ bool HttpModule::end(const char* fqn, int, SnortConfig*)
                 params->uri_param.iis_unicode_code_page);
     }
 
-    params->js_norm_param.js_norm = new HttpJsNorm(params->uri_param,
-        params->js_norm_param.js_norm_bytes_depth, params->js_norm_param.js_identifier_depth,
-        params->js_norm_param.max_template_nesting, params->js_norm_param.max_bracket_depth,
-        params->js_norm_param.max_scope_depth, params->js_norm_param.ignored_ids);
+    params->js_norm_param.js_norm = new HttpJsNorm(params->uri_param, params->js_norm_param);
 
     params->script_detection_handle = script_detection_handle;
 
index 77063b916cfd2977d95b101726f429ce6b8552f5..35dce8d005aa0e76e4e62054303956f4ded28438 100755 (executable)
@@ -74,6 +74,7 @@ public:
         uint32_t max_bracket_depth = 256;
         uint32_t max_scope_depth = 256;
         std::unordered_set<std::string> ignored_ids;
+        std::unordered_set<std::string> ignored_props;
         int max_javascript_whitespaces = 200;
         class HttpJsNorm* js_norm = nullptr;
     };
index 13a1ce9324bf8d97b45a4c54e67cfd0770502e86..3bbd67111494596b4ed1bcdd5998d158d8a37bb1 100755 (executable)
@@ -68,13 +68,10 @@ int32_t substr_to_code(const uint8_t*, const int32_t, const StrCode []) { return
 long HttpTestManager::print_amount {};
 bool HttpTestManager::print_hex {};
 
-HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_,
-    int32_t identifier_depth_, uint8_t max_template_nesting_, uint32_t max_bracket_depth_,
-    uint32_t max_scope_depth_, const std::unordered_set<std::string>& ignored_ids_) :
-    uri_param(uri_param_), normalization_depth(normalization_depth_),
-    identifier_depth(identifier_depth_), max_template_nesting(max_template_nesting_),
-    max_bracket_depth(max_bracket_depth_), max_scope_depth(max_scope_depth_),
-    ignored_ids(ignored_ids_), mpse_otag(nullptr), mpse_attr(nullptr), mpse_type(nullptr) {}
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_,
+    const HttpParaList::JsNormParam& js_norm_param_) :
+    uri_param(uri_param_), js_norm_param(js_norm_param_), mpse_otag(nullptr), mpse_attr(nullptr),
+    mpse_type(nullptr) {}
 HttpJsNorm::~HttpJsNorm() = default;
 void HttpJsNorm::configure(){}
 int64_t Parameter::get_int(char const*) { return 0; }
index b6823f70204c3a13533bd267815bcf3a71a44b17..62fa8f0f34301d87f20478108594bdcad2512550 100755 (executable)
@@ -57,13 +57,10 @@ void DecodeConfig::set_decompress_vba(bool) {}
 void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { }
 void show_stats(PegCount*, const PegInfo*, const IndexVec&, const char*, FILE*) { }
 
-HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_,
-    int32_t identifier_depth_, uint8_t max_template_nesting_, uint32_t max_bracket_depth_,
-    uint32_t max_scope_depth_, const std::unordered_set<std::string>& ignored_ids_) :
-    uri_param(uri_param_), normalization_depth(normalization_depth_),
-    identifier_depth(identifier_depth_), max_template_nesting(max_template_nesting_),
-    max_bracket_depth(max_bracket_depth_), max_scope_depth(max_scope_depth_),
-    ignored_ids(ignored_ids_), mpse_otag(nullptr), mpse_attr(nullptr), mpse_type(nullptr) {}
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_,
+    const HttpParaList::JsNormParam& js_norm_param_) :
+    uri_param(uri_param_), js_norm_param(js_norm_param_), mpse_otag(nullptr), mpse_attr(nullptr),
+    mpse_type(nullptr) {}
 HttpJsNorm::~HttpJsNorm() = default;
 void HttpJsNorm::configure() {}
 int64_t Parameter::get_int(char const*) { return 0; }
index e6900c8d79804ffe6088ecba7f62a10bf3605a3d..aa6e36a966c861e329e322b01390b6753427d70c 100644 (file)
@@ -48,6 +48,10 @@ public:
 #define NORM_NAME_SIZE 9 // size of the normalized form plus null symbol
 #define NORM_NAME_CNT 65536
 
+#define TYPE_NORMALIZED     1
+#define TYPE_IGNORED_ID     2
+#define TYPE_IGNORED_PROP   4
+
 static char norm_names[NORM_NAME_SIZE * NORM_NAME_CNT];
 
 static void init_norm_names()
@@ -79,50 +83,58 @@ static void init_norm_names()
 }
 
 JSIdentifierCtx::JSIdentifierCtx(int32_t depth, uint32_t max_scope_depth,
-    const std::unordered_set<std::string>& ignore_list)
-    : ignore_list(ignore_list), max_scope_depth(max_scope_depth)
+    const std::unordered_set<std::string>& ignored_ids_list,
+    const std::unordered_set<std::string>& ignored_props_list)
+    : ignored_ids_list(ignored_ids_list), ignored_props_list(ignored_props_list), 
+    max_scope_depth(max_scope_depth)
 {
     init_norm_names();
 
-    memset(id_fast, 0, sizeof(id_fast));
     norm_name = norm_names;
     norm_name_end = norm_names + NORM_NAME_SIZE * std::min(depth, NORM_NAME_CNT);
     scopes.emplace_back(JSProgramScopeType::GLOBAL);
 
-    for (const auto& iid : ignore_list)
-        if (iid.length() == 1)
-            id_fast[(unsigned)iid[0]] = iid.c_str();
-        else
-            id_names[iid] = iid.c_str();
+    init_ignored_names();
 }
 
-const char* JSIdentifierCtx::substitute(unsigned char c)
+const char* JSIdentifierCtx::substitute(unsigned char c, bool is_property)
 {
     auto p = id_fast[c];
-    if (p)
-        return p;
-
-    if (norm_name >= norm_name_end)
-        return nullptr;
-
-    auto n = norm_name;
-    norm_name += NORM_NAME_SIZE;
-    HttpModule::increment_peg_counts(HttpEnums::PEG_JS_IDENTIFIER);
+    if (is_substituted(p, is_property))
+        return is_property ? p.prop_name : p.id_name;
 
-    return id_fast[c] = n;
+    return acquire_norm_name(id_fast[c]);
 }
 
-const char* JSIdentifierCtx::substitute(const char* id_name)
+const char* JSIdentifierCtx::substitute(const char* id_name, bool is_property)
 {
     assert(*id_name);
 
     if (id_name[1] == '\0')
-        return substitute(*id_name);
+        return substitute(*id_name, is_property);
 
     const auto it = id_names.find(id_name);
-    if (it != id_names.end())
-        return it->second;
+    if (it != id_names.end() && is_substituted(it->second, is_property))
+        return is_property ? it->second.prop_name : it->second.id_name;
 
+    return acquire_norm_name(id_names[id_name]);
+}
+
+bool JSIdentifierCtx::is_ignored(const char* id_name) const
+{
+    return id_name < norm_names ||
+        id_name >= norm_names + NORM_NAME_SIZE * NORM_NAME_CNT;
+}
+
+bool JSIdentifierCtx::is_substituted(const NormId& id, bool is_property)
+{
+    return ((id.type & TYPE_NORMALIZED) != 0) ||
+        (!is_property && ((id.type & TYPE_IGNORED_ID) != 0)) ||
+        (is_property && ((id.type & TYPE_IGNORED_PROP) != 0));
+}
+
+const char* JSIdentifierCtx::acquire_norm_name(NormId& id)
+{
     if (norm_name >= norm_name_end)
         return nullptr;
 
@@ -130,13 +142,39 @@ const char* JSIdentifierCtx::substitute(const char* id_name)
     norm_name += NORM_NAME_SIZE;
     HttpModule::increment_peg_counts(HttpEnums::PEG_JS_IDENTIFIER);
 
-    return id_names[id_name] = n;
+    if (id.prop_name || id.id_name)
+    {
+        id.type |= TYPE_NORMALIZED;
+        if ((id.type & TYPE_IGNORED_ID) != 0)
+            return id.prop_name = n;
+        else if ((id.type & TYPE_IGNORED_PROP) != 0)
+            return id.id_name = n;
+    }
+
+    return (id = {n, n, TYPE_NORMALIZED}).id_name;
 }
 
-bool JSIdentifierCtx::is_ignored(const char* id_name) const
+void JSIdentifierCtx::init_ignored_names()
 {
-    return id_name < norm_names ||
-        id_name >= norm_names + NORM_NAME_SIZE * NORM_NAME_CNT;
+    for (const auto& iid : ignored_ids_list)
+        if (iid.length() == 1)
+            id_fast[(unsigned)iid[0]] = {iid.c_str(), nullptr, TYPE_IGNORED_ID};
+        else
+            id_names[iid] = {iid.c_str(), nullptr, TYPE_IGNORED_ID};
+
+    for (const auto& iprop : ignored_props_list)
+    {
+        if (iprop.length() == 1)
+        {
+            id_fast[(unsigned)iprop[0]].prop_name = iprop.c_str();
+            id_fast[(unsigned)iprop[0]].type |= TYPE_IGNORED_PROP;
+        }
+        else
+        {
+            id_names[iprop].prop_name = iprop.c_str();
+            id_names[iprop].type |= TYPE_IGNORED_PROP;
+        }
+    }
 }
 
 bool JSIdentifierCtx::scope_push(JSProgramScopeType t)
@@ -164,17 +202,12 @@ bool JSIdentifierCtx::scope_pop(JSProgramScopeType t)
 
 void JSIdentifierCtx::reset()
 {
-    memset(id_fast, 0, sizeof(id_fast));
+    memset(&id_fast, 0, sizeof(id_fast));
     norm_name = norm_names;
     id_names.clear();
     scopes.clear();
     scopes.emplace_back(JSProgramScopeType::GLOBAL);
-
-    for (const auto& iid : ignore_list)
-        if (iid.length() == 1)
-            id_fast[(unsigned)iid[0]] = iid.c_str();
-        else
-            id_names[iid] = iid.c_str();
+    init_ignored_names();
 }
 
 void JSIdentifierCtx::add_alias(const char* alias, const std::string&& value)
index e0aa57d4fa209751e62f0efe2890195befe2540d..4d648f27e0af11123e55453eba72948c09ff167e 100644 (file)
@@ -39,7 +39,7 @@ class JSIdentifierCtxBase
 public:
     virtual ~JSIdentifierCtxBase() = default;
 
-    virtual const char* substitute(const char* identifier) = 0;
+    virtual const char* substitute(const char* identifier, bool is_property) = 0;
     virtual void add_alias(const char* alias, const std::string&& value) = 0;
     virtual const char* alias_lookup(const char* alias) const = 0;
     virtual bool is_ignored(const char* identifier) const = 0;
@@ -56,9 +56,10 @@ class JSIdentifierCtx : public JSIdentifierCtxBase
 {
 public:
     JSIdentifierCtx(int32_t depth, uint32_t max_scope_depth,
-        const std::unordered_set<std::string>& ignore_list);
+        const std::unordered_set<std::string>& ignored_ids_list,
+        const std::unordered_set<std::string>& ignored_props_list);
 
-    virtual const char* substitute(const char* identifier) override;
+    virtual const char* substitute(const char* identifier, bool is_property) override;
     virtual void add_alias(const char* alias, const std::string&& value) override;
     virtual const char* alias_lookup(const char* alias) const override;
     virtual bool is_ignored(const char* identifier) const override;
@@ -75,10 +76,18 @@ public:
         (sizeof(ProgramScope) * 3)); }
 
 private:
+
+    struct NormId
+    {
+        const char* id_name = nullptr;
+        const char* prop_name = nullptr;
+        uint8_t type = 0;
+    };
+    
     using Alias = std::vector<std::string>;
     using AliasRef = std::list<Alias*>;
     using AliasMap = std::unordered_map<std::string, Alias>;
-    using NameMap = std::unordered_map<std::string, const char*>;
+    using NameMap = std::unordered_map<std::string, NormId>;
 
     class ProgramScope
     {
@@ -100,15 +109,19 @@ private:
         AliasRef to_remove{};
     };
 
-    inline const char* substitute(unsigned char c);
+    inline const char* substitute(unsigned char c, bool is_property);
+    inline bool is_substituted(const NormId& id, bool is_property);
+    inline const char* acquire_norm_name(NormId& id);
+    inline void init_ignored_names();
 
     // do not swap next two lines, the destructor frees them in the reverse order
     AliasMap aliases;
     std::list<ProgramScope> scopes;
 
-    const char* id_fast[256];
+    NormId id_fast[256];
     NameMap id_names;
-    const std::unordered_set<std::string>& ignore_list;
+    const std::unordered_set<std::string>& ignored_ids_list;
+    const std::unordered_set<std::string>& ignored_props_list;
 
     const char* norm_name;
     const char* norm_name_end;
index fea92f9ee2bba21e0dd808d53bb57f578337d9ee..569bf4b0feab1f324a7f6529e4daec18388e0ace 100644 (file)
@@ -1500,7 +1500,7 @@ JSTokenizer::JSRet JSTokenizer::do_identifier_substitution(const char* lexeme, b
 
     set_ident_norm(true);
 
-    const char* name = ident_ctx.substitute(lexeme);
+    const char* name = ident_ctx.substitute(lexeme, id_part);
 
     if (!name)
     {
@@ -1511,19 +1511,11 @@ JSTokenizer::JSRet JSTokenizer::do_identifier_substitution(const char* lexeme, b
 
     if (ident_ctx.is_ignored(name))
     {
-        if (id_part)
-        {
-            std::string n(name);
-            n.push_back('+'); // any illegal symbol as a part of ID name
-            name = ident_ctx.substitute(n.c_str());
-        }
-        else
-        {
+        if (!id_part)
             ignored_id_pos = yyout.rdbuf()->pubseekoff(0, yyout.cur, std::ios_base::out);
-            set_ident_norm(false);
-            yyout << name;
-            return EOS;
-        }
+        set_ident_norm(false);
+        yyout << name;
+        return EOS;
     }
 
     const char* alias = id_part ? nullptr : ident_ctx.alias_lookup(lexeme);
@@ -2076,7 +2068,8 @@ void JSTokenizer::dealias_clear_mutated(bool id_continue)
 {
     if (!id_continue && prefix_increment && dealias_stored)
     {
-        ident_ctx.add_alias(last_dealiased.c_str(), std::string(ident_ctx.substitute(last_dealiased.c_str())));
+        ident_ctx.add_alias(last_dealiased.c_str(),
+            std::string(ident_ctx.substitute(last_dealiased.c_str(), false)));
     }
     dealias_stored = false;
     prefix_increment = false;
@@ -2086,7 +2079,8 @@ void JSTokenizer::dealias_increment()
 {
     if (dealias_stored)
     {
-        ident_ctx.add_alias(last_dealiased.c_str(), std::string(ident_ctx.substitute(last_dealiased.c_str())));
+        ident_ctx.add_alias(last_dealiased.c_str(),
+            std::string(ident_ctx.substitute(last_dealiased.c_str(), false)));
     }
     prefix_increment = token != IDENTIFIER && token != CLOSING_BRACKET;
     dealias_stored = false;
@@ -2118,7 +2112,7 @@ void JSTokenizer::dealias_identifier(bool id_part, bool assignment_start)
     {
         auto dealias = ident_ctx.alias_lookup(lexeme);
         if ((!ident_norm() && id_part) ||
-            (ident_ctx.is_ignored(ident_ctx.substitute(lexeme)) && !id_part))
+            (!id_part && ident_ctx.is_ignored(ident_ctx.substitute(lexeme, false))))
             aliased << YYText();
         else if (dealias)
             aliased << dealias;
@@ -2136,7 +2130,10 @@ void JSTokenizer::dealias_equals(bool complex_assignment)
         if (complex_assignment)
         {
             if (ident_ctx.alias_lookup(alias.c_str()))
-                ident_ctx.add_alias(alias.c_str(), std::string(ident_ctx.substitute(alias.c_str())));
+            {
+                ident_ctx.add_alias(alias.c_str(),
+                    std::string(ident_ctx.substitute(alias.c_str(), false)));
+            }
             alias_state = ALIAS_NONE;
         }
         else
@@ -2150,7 +2147,10 @@ void JSTokenizer::dealias_reset()
     {
         if (alias_state == ALIAS_VALUE || alias_state == ALIAS_EQUALS)
             if (ident_ctx.alias_lookup(alias.c_str()))
-                ident_ctx.add_alias(alias.c_str(), std::string(ident_ctx.substitute(alias.c_str())));
+            {
+                ident_ctx.add_alias(alias.c_str(),
+                    std::string(ident_ctx.substitute(alias.c_str(), false)));
+            }
         alias_state = ALIAS_NONE;
     }
 }
index 6f781174c0433fff78ef1c16e1469d5e5718641e..9513f54cadff73ea56d6208f5f909eebaba0e178 100644 (file)
 #define DEPTH 65536
 #define SCOPE_DEPTH 256
 
-static const std::unordered_set<std::string> s_ignored_ids { "console" };
+static const std::unordered_set<std::string> s_ignored_ids { "console", "v" };
+static const std::unordered_set<std::string> s_ignored_props { "watch", "w" };
 
 TEST_CASE("JSIdentifierCtx::substitute()", "[JSIdentifierCtx]")
 {
     SECTION("same name")
     {
-        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
 
-        CHECK(!strcmp(ident_ctx.substitute("a"), "var_0000"));
-        CHECK(!strcmp(ident_ctx.substitute("a"), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("a", false), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("a", false), "var_0000"));
     }
     SECTION("different names")
     {
-        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
 
-        CHECK(!strcmp(ident_ctx.substitute("a"), "var_0000"));
-        CHECK(!strcmp(ident_ctx.substitute("b"), "var_0001"));
-        CHECK(!strcmp(ident_ctx.substitute("a"), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("a", false), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("b", false), "var_0001"));
+        CHECK(!strcmp(ident_ctx.substitute("a", false), "var_0000"));
     }
     SECTION("depth reached")
     {
-        JSIdentifierCtx ident_ctx(2, SCOPE_DEPTH, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(2, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
 
-        CHECK(!strcmp(ident_ctx.substitute("a"), "var_0000"));
-        CHECK(!strcmp(ident_ctx.substitute("b"), "var_0001"));
-        CHECK(ident_ctx.substitute("c") == nullptr);
-        CHECK(ident_ctx.substitute("d") == nullptr);
-        CHECK(!strcmp(ident_ctx.substitute("a"), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("a", false), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("b", false), "var_0001"));
+        CHECK(ident_ctx.substitute("c", false) == nullptr);
+        CHECK(ident_ctx.substitute("d", false) == nullptr);
+        CHECK(!strcmp(ident_ctx.substitute("a", false), "var_0000"));
     }
     SECTION("max names")
     {
-        JSIdentifierCtx ident_ctx(DEPTH + 2, SCOPE_DEPTH, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(DEPTH + 2, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
 
         std::vector<std::string> n, e;
         n.reserve(DEPTH + 2);
@@ -82,27 +83,66 @@ TEST_CASE("JSIdentifierCtx::substitute()", "[JSIdentifierCtx]")
         }
 
         for (int it = 0; it < DEPTH; ++it)
-            CHECK(!strcmp(ident_ctx.substitute(n[it].c_str()), e[it].c_str()));
+            CHECK(!strcmp(ident_ctx.substitute(n[it].c_str(), false), e[it].c_str()));
 
-        CHECK(ident_ctx.substitute(n[DEPTH].c_str()) == nullptr);
-        CHECK(ident_ctx.substitute(n[DEPTH + 1].c_str()) == nullptr);
+        CHECK(ident_ctx.substitute(n[DEPTH].c_str(), false) == nullptr);
+        CHECK(ident_ctx.substitute(n[DEPTH + 1].c_str(), false) == nullptr);
+    }
+    SECTION("ignored identifier - single char")
+    {
+        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
+
+        CHECK(!strcmp(ident_ctx.substitute("v", false), "v"));
+        CHECK(!strcmp(ident_ctx.substitute("v", true), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("w", false), "var_0001"));
+        CHECK(!strcmp(ident_ctx.substitute("w", true), "w"));
+    }
+    SECTION("ignored identifier - multiple chars")
+    {
+        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
+
+        CHECK(!strcmp(ident_ctx.substitute("console", false), "console"));
+        CHECK(!strcmp(ident_ctx.substitute("console", true), "var_0000"));
+        CHECK(!strcmp(ident_ctx.substitute("watch", false), "var_0001"));
+        CHECK(!strcmp(ident_ctx.substitute("watch", true), "watch"));
     }
 }
 
 TEST_CASE("JSIdentifierCtx::is_ignored()", "[JSIdentifierCtx]")
 {
-    JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids);
+    SECTION("single char identifier")
+    {
+        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
+
+        auto v1 = ident_ctx.substitute("v", false);
+        auto v2 = ident_ctx.substitute("a", false);
+        auto v3 = ident_ctx.substitute("w", false);
+        auto v4 = ident_ctx.substitute("w", true);
+
+        CHECK(ident_ctx.is_ignored(v1) == true);
+        CHECK(ident_ctx.is_ignored(v2) == false);
+        CHECK(ident_ctx.is_ignored(v3) == false);
+        CHECK(ident_ctx.is_ignored(v4) == true);
+    }
+    SECTION("multiple chars identifier")
+    {
+        JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
 
-    auto v1 = ident_ctx.substitute("console");
-    auto v2 = ident_ctx.substitute("foo");
+        auto v1 = ident_ctx.substitute("console", false);
+        auto v2 = ident_ctx.substitute("foo", false);
+        auto v3 = ident_ctx.substitute("watch", false);
+        auto v4 = ident_ctx.substitute("watch", true);
 
-    CHECK(ident_ctx.is_ignored(v1) == true);
-    CHECK(ident_ctx.is_ignored(v2) == false);
+        CHECK(ident_ctx.is_ignored(v1) == true);
+        CHECK(ident_ctx.is_ignored(v2) == false);
+        CHECK(ident_ctx.is_ignored(v3) == false);
+        CHECK(ident_ctx.is_ignored(v4) == true);
+    }
 }
 
 TEST_CASE("JSIdentifierCtx::scopes", "[JSIdentifierCtx]")
 {
-    JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(DEPTH, SCOPE_DEPTH, s_ignored_ids, s_ignored_props);
 
     SECTION("scope stack")
     {
@@ -158,7 +198,7 @@ TEST_CASE("JSIdentifierCtx::scopes", "[JSIdentifierCtx]")
     }
     SECTION("scope max nesting")
     {
-        JSIdentifierCtx ident_ctx_limited(DEPTH, 2, s_ignored_ids);
+        JSIdentifierCtx ident_ctx_limited(DEPTH, 2, s_ignored_ids, s_ignored_props);
 
         CHECK(ident_ctx_limited.scope_push(JSProgramScopeType::FUNCTION));
         CHECK(ident_ctx_limited.scope_check({GLOBAL, FUNCTION}));
index 36dffebc139a22b5a707e34a3193e145350fa51d..44c02b4ebcbae4054f996f0a7fa9b0a22d847353 100644 (file)
@@ -114,7 +114,7 @@ using namespace snort;
     {                                                               \
         char dst1[sizeof(exp1)];                                    \
                                                                     \
-        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids); \
+        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props); \
         JSNormalizer norm(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth); \
                                                                     \
         DO(src1, sizeof(src1) - 1, dst1, sizeof(dst1) - 1);         \
@@ -128,7 +128,7 @@ using namespace snort;
         char dst1[sizeof(exp1)];                                    \
         char dst2[sizeof(exp2)];                                    \
                                                                     \
-        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids); \
+        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props); \
         JSNormalizer norm(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth); \
                                                                     \
         DO(src1, sizeof(src1) - 1, dst1, sizeof(dst1) - 1);         \
@@ -363,7 +363,7 @@ using namespace snort;
 
 #define NORM_COMBINED_S_2(src1, src2, exp)                              \
     {                                                                   \
-        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids); \
+        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props); \
         JSNormalizer norm(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth); \
                                                                         \
         auto ret = norm.normalize(src1, sizeof(src1) - 1);              \
@@ -2764,7 +2764,7 @@ TEST_CASE("split and continuation combined", "[JSNormalizer]")
         char dst3[sizeof(exp3)];
         char dst4[sizeof(exp4)];
 
-        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
         JSNormalizer norm(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
 
         DO(src1, sizeof(src1) - 1, dst1, sizeof(dst1) - 1);
@@ -2799,7 +2799,7 @@ TEST_CASE("split and continuation combined", "[JSNormalizer]")
         char dst2[sizeof(exp2)];
         char dst3[sizeof(exp3)];
 
-        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
         JSNormalizer norm(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
 
         TRY(src1, sizeof(src1) - 1, dst1, sizeof(dst1) - 1, JSTokenizer::SCRIPT_CONTINUE);
@@ -4038,6 +4038,274 @@ TEST_CASE("ignored identifier scope tracking", "[JSNormalizer]")
     }
 }
 
+TEST_CASE("ignored properties", "[JSNormalizer]")
+{
+    SECTION("basic")
+    {
+        const char dat1[] = "foo.bar ;";
+        const char dat2[] = "foo.bar() ;";
+        const char dat3[] = "foo.watch ;";
+        const char dat4[] = "foo.unwatch() ;";
+        const char dat5[] = "console.watch ;";
+        const char dat6[] = "console.unwatch() ;";
+        const char dat7[] = "console.foo.watch ;";
+        const char dat8[] = "console.foo.unwatch() ;";
+        const char dat9[] = "foo.console.watch ;";
+        const char dat10[] = "foo.console.unwatch() ;";
+
+        const char dat11[] = "foo['bar'] ;";
+        const char dat12[] = "foo[\"bar\"]() ;";
+        const char dat13[] = "foo['watch'] ;";
+        const char dat14[] = "foo[\"unwatch\"]() ;";
+        const char dat15[] = "console['watch'] ;";
+        const char dat16[] = "console[\"unwatch\"]() ;";
+        const char dat17[] = "console['foo']['watch'] ;";
+        const char dat18[] = "console[\"foo\"][\"unwatch\"]() ;";
+        const char dat19[] = "foo['console']['watch'] ;";
+        const char dat20[] = "foo[\"console\"][\"unwatch\"]() ;";
+
+        const char exp1[] = "var_0000.var_0001;";
+        const char exp2[] = "var_0000.var_0001();";
+        const char exp3[] = "var_0000.watch;";
+        const char exp4[] = "var_0000.unwatch();";
+        const char exp5[] = "console.watch;";
+        const char exp6[] = "console.unwatch();";
+        const char exp7[] = "console.foo.watch;";
+        const char exp8[] = "console.foo.unwatch();";
+        const char exp9[] = "var_0000.var_0001.watch;";
+        const char exp10[] = "var_0000.var_0001.unwatch();";
+
+        const char exp11[] = "var_0000['bar'];";
+        const char exp12[] = "var_0000[\"bar\"]();";
+        const char exp13[] = "var_0000['watch'];";
+        const char exp14[] = "var_0000[\"unwatch\"]();";
+        const char exp15[] = "console['watch'];";
+        const char exp16[] = "console[\"unwatch\"]();";
+        const char exp17[] = "console['foo']['watch'];";
+        const char exp18[] = "console[\"foo\"][\"unwatch\"]();";
+        const char exp19[] = "var_0000['console']['watch'];";
+        const char exp20[] = "var_0000[\"console\"][\"unwatch\"]();";
+
+        NORMALIZE_S(dat1, exp1);
+        NORMALIZE_S(dat2, exp2);
+        NORMALIZE_S(dat3, exp3);
+        NORMALIZE_S(dat4, exp4);
+        NORMALIZE_S(dat5, exp5);
+        NORMALIZE_S(dat6, exp6);
+        NORMALIZE_S(dat7, exp7);
+        NORMALIZE_S(dat8, exp8);
+        NORMALIZE_S(dat9, exp9);
+        NORMALIZE_S(dat10, exp10);
+
+        NORMALIZE_S(dat11, exp11);
+        NORMALIZE_S(dat12, exp12);
+        NORMALIZE_S(dat13, exp13);
+        NORMALIZE_S(dat14, exp14);
+        NORMALIZE_S(dat15, exp15);
+        NORMALIZE_S(dat16, exp16);
+        NORMALIZE_S(dat17, exp17);
+        NORMALIZE_S(dat18, exp18);
+        NORMALIZE_S(dat19, exp19);
+        NORMALIZE_S(dat20, exp20);
+    }
+
+    SECTION("chain tracking")
+    {
+        const char dat1[] = "foo.watch.bar ;";
+        const char dat2[] = "foo['watch'].bar ;";
+        const char dat3[] = "foo.bar.watch.bar ;";
+        const char dat4[] = "foo['bar'].watch['bar'] ;";
+        const char dat5[] = "foo['bar'].watch['bar'].baz ;";
+
+        const char dat6[] = "foo.unwatch().bar ;";
+        const char dat7[] = "foo['unwatch']().bar ;";
+        const char dat8[] = "foo.bar.unwatch().bar ;";
+        const char dat9[] = "foo['bar'].unwatch()['bar'] ;";
+        const char dat10[] = "foo['bar'].unwatch()['bar'].baz ;";
+
+        const char dat11[] = "foo . watch \n . bar ;";
+        const char dat12[] = "foo ['watch'] \n . bar ;";
+        const char dat13[] = "foo . /*multiline*/ watch //oneline\n . bar ;";
+
+        const char dat14[] = "foo . unwatch () \n . bar ;";
+        const char dat15[] = "foo ['unwatch'] () \n . bar ;";
+        const char dat16[] = "foo /*multiline*/ . unwatch ( ) . // oneline \n bar ;";
+
+        const char dat17[] = "foo . + watch . bar ;";
+        const char dat18[] = "foo . + ['watch'] . bar ;";
+
+        const char dat19[] = "foo . + unwatch() . bar ;";
+        const char dat20[] = "foo . + ['unwatch']() . bar ;";
+
+        // FIXIT-L: add support for proper tracking of bracket accessors.
+        // Current behavior: foo['watch'].bar -> var_0000['watch'].var_0001
+        // Expected behavior: foo['watch'].bar -> var_0000['watch'].bar
+        const char exp1[] = "var_0000.watch.bar;";
+        const char exp2[] = "var_0000['watch'].var_0001;";
+        const char exp3[] = "var_0000.var_0001.watch.bar;";
+        const char exp4[] = "var_0000['bar'].watch['bar'];";
+        const char exp5[] = "var_0000['bar'].watch['bar'].baz;";
+
+        const char exp6[] = "var_0000.unwatch().bar;";
+        const char exp7[] = "var_0000['unwatch']().var_0001;";
+        const char exp8[] = "var_0000.var_0001.unwatch().bar;";
+        const char exp9[] = "var_0000['bar'].unwatch()['bar'];";
+        const char exp10[] = "var_0000['bar'].unwatch()['bar'].baz;";
+
+        const char exp11[] = "var_0000.watch.bar;";
+        const char exp12[] = "var_0000['watch'].var_0001;";
+        const char exp13[] = "var_0000.watch.bar;";
+
+        const char exp14[] = "var_0000.unwatch().bar;";
+        const char exp15[] = "var_0000['unwatch']().var_0001;";
+        const char exp16[] = "var_0000.unwatch().bar;";
+
+        const char exp17[] = "var_0000.+var_0001.var_0002;";
+        const char exp18[] = "var_0000.+['watch'].var_0001;";
+
+        const char exp19[] = "var_0000.+var_0001().var_0002;";
+        const char exp20[] = "var_0000.+['unwatch']().var_0001;";
+
+        NORMALIZE_S(dat1, exp1);
+        NORMALIZE_S(dat2, exp2);
+        NORMALIZE_S(dat3, exp3);
+        NORMALIZE_S(dat4, exp4);
+        NORMALIZE_S(dat5, exp5);
+
+        NORMALIZE_S(dat6, exp6);
+        NORMALIZE_S(dat7, exp7);
+        NORMALIZE_S(dat8, exp8);
+        NORMALIZE_S(dat9, exp9);
+        NORMALIZE_S(dat10, exp10);
+
+        NORMALIZE_S(dat11, exp11);
+        NORMALIZE_S(dat12, exp12);
+        NORMALIZE_S(dat13, exp13);
+
+        NORMALIZE_S(dat14, exp14);
+        NORMALIZE_S(dat15, exp15);
+        NORMALIZE_S(dat16, exp16);
+
+        NORMALIZE_S(dat17, exp17);
+        NORMALIZE_S(dat18, exp18);
+
+        NORMALIZE_S(dat19, exp19);
+        NORMALIZE_S(dat20, exp20);
+    }
+
+    SECTION("scope tracking")
+    {
+        const char dat1[] = "foo.(watch).bar ;";
+        const char dat2[] = "foo(['watch']).bar ;";
+
+        const char dat3[] = "foo.bar(baz.unwatch.eval).eval ;";
+        const char dat4[] = "foo.bar(baz['unwatch'].eval).eval ;";
+
+        const char exp1[] = "var_0000.(var_0001).var_0002;";
+        const char exp2[] = "var_0000(['watch']).var_0001;";
+
+        const char exp3[] = "var_0000.var_0001(var_0002.unwatch.eval).var_0003;";
+        const char exp4[] = "var_0000.var_0001(var_0002['unwatch'].var_0003).var_0003;";
+
+        NORMALIZE_S(dat1, exp1);
+        NORMALIZE_S(dat2, exp2);
+
+        NORMALIZE_S(dat3, exp3);
+        NORMALIZE_S(dat4, exp4);
+    }
+
+    SECTION("corner cases")
+    {
+        const char dat1[] = ".watch ;";
+        const char dat2[] = ".unwatch() ;";
+
+        const char dat3[] = "'foo'.watch ;";
+        const char dat4[] = "\"foo\".unwatch() ;";
+
+        const char dat5[] = "''.split('').reverse().join('') ;";
+        const char dat6[] = "\"\".split(\"\").reverse().join(\"\") ;";
+
+        const char dat7[] = "watch () ;";
+        const char dat8[] = "watch.watch() ;";
+
+        // 'name' is present in both ignore lists
+        const char dat9[] = "name.foo ;";
+        const char dat10[] = "foo.name ;";
+        const char dat11[] = "name.name ;";
+        const char dat12[] = "name ;";
+
+        const char dat13[] = "foo.foo ;";
+        const char dat14[] = "console.console; console;";
+        const char dat15[] = "watch.watch; watch;";
+        const char dat16[] = "foo.console; console.foo; foo.watch; watch.foo ;";
+        const char dat17[] = "console.foo; foo.console; watch.foo; foo.watch ;";
+
+        const char dat18[] = "a.a ;";
+        const char dat19[] = "u.u; u;";
+        const char dat20[] = "w.w; w;";
+        const char dat21[] = "a.u; u.a; a.w; w.a ;";
+        const char dat22[] = "u.a; a.u; w.a; a.w ;";
+
+        const char exp1[] = ".watch;";
+        const char exp2[] = ".unwatch();";
+
+        const char exp3[] = "'foo'.watch;";
+        const char exp4[] = "\"foo\".unwatch();";
+
+        const char exp5[] = "''.split('').reverse().join('');";
+        const char exp6[] = "\"\".split(\"\").reverse().join(\"\");";
+
+        const char exp7[] = "var_0000();";
+        const char exp8[] = "var_0000.watch();";
+
+        const char exp9[] = "name.foo;";
+        const char exp10[] = "var_0000.name;";
+        const char exp11[] = "name.name;";
+        const char exp12[] = "name;";
+
+        const char exp13[] = "var_0000.var_0000;";
+        const char exp14[] = "console.console;console;";
+        const char exp15[] = "var_0000.watch;var_0000;";
+        const char exp16[] = "var_0000.var_0001;console.foo;var_0000.watch;var_0002.var_0000;";
+        const char exp17[] = "console.foo;var_0000.var_0001;var_0002.var_0000;var_0000.watch;";
+
+        const char exp18[] = "var_0000.var_0000;";
+        const char exp19[] = "u.u;u;";
+        const char exp20[] = "var_0000.w;var_0000;";
+        const char exp21[] = "var_0000.var_0001;u.a;var_0000.w;var_0002.var_0000;";
+        const char exp22[] = "u.a;var_0000.var_0001;var_0002.var_0000;var_0000.w;";
+
+        NORMALIZE_S(dat1, exp1);
+        NORMALIZE_S(dat2, exp2);
+
+        NORMALIZE_S(dat3, exp3);
+        NORMALIZE_S(dat4, exp4);
+
+        NORMALIZE_S(dat5, exp5);
+        NORMALIZE_S(dat6, exp6);
+
+        NORMALIZE_S(dat7, exp7);
+        NORMALIZE_S(dat8, exp8);
+
+        NORMALIZE_S(dat9, exp9);
+        NORMALIZE_S(dat10, exp10);
+        NORMALIZE_S(dat11, exp11);
+        NORMALIZE_S(dat12, exp12);
+
+        NORMALIZE_S(dat13, exp13);
+        NORMALIZE_S(dat14, exp14);
+        NORMALIZE_S(dat15, exp15);
+        NORMALIZE_S(dat16, exp16);
+        NORMALIZE_S(dat17, exp17);
+
+        NORMALIZE_S(dat18, exp18);
+        NORMALIZE_S(dat19, exp19);
+        NORMALIZE_S(dat20, exp20);
+        NORMALIZE_S(dat21, exp21);
+        NORMALIZE_S(dat22, exp22);
+    }
+}
+
 TEST_CASE("ignored identifier split", "[JSNormalizer]")
 {
 
@@ -4230,6 +4498,194 @@ TEST_CASE("ignored identifier split", "[JSNormalizer]")
     }
 }
 
+TEST_CASE("ignored properties split", "[JSNormalizer]")
+{
+
+#if JSTOKENIZER_MAX_STATES != 8
+#error "ignored properties split" tests are designed for 8 states depth
+#endif
+
+    SECTION("a standalone property")
+    {
+        const char dat1[] = "foo.un";
+        const char dat2[] = "watch ;";
+        const char exp1[] = "var_0000.var_0001";
+        const char exp2[] = "unwatch;";
+        const char exp_comb_1[] = "var_0000.unwatch;";
+
+        const char dat3[] = "foo. un";
+        const char dat4[] = "watch () ;";
+        const char exp3[] = "var_0000.var_0001";
+        const char exp4[] = "unwatch();";
+        const char exp_comb_2[] = "var_0000.unwatch();";
+
+        const char dat5[] = "fo";
+        const char dat6[] = "o . watch ;";
+        const char exp5[] = "var_0000";
+        const char exp6[] = "var_0001.watch;";
+        const char exp_comb_3[] = "var_0001.watch;";
+
+        const char dat7[] = "foo. ";
+        const char dat8[] = "watch ;";
+        const char exp7[] = "var_0000.";
+        const char exp8[] = "watch;";
+        const char exp_comb_4[] = "var_0000.watch;";
+
+        const char dat9[] = "foo ";
+        const char dat10[] = ". watch ;";
+        const char exp9[] = "var_0000";
+        const char exp10[] = ".watch;";
+        const char exp_comb_5[] = "var_0000.watch;";
+
+        NORMALIZE_T(dat1, dat2, exp1, exp2);
+        NORM_COMBINED_S_2(dat1, dat2, exp_comb_1);
+
+        NORMALIZE_T(dat3, dat4, exp3, exp4);
+        NORM_COMBINED_S_2(dat3, dat4, exp_comb_2);
+
+        NORMALIZE_T(dat5, dat6, exp5, exp6);
+        NORM_COMBINED_S_2(dat5, dat6, exp_comb_3);
+
+        NORMALIZE_T(dat7, dat8, exp7, exp8);
+        NORM_COMBINED_S_2(dat7, dat8, exp_comb_4);
+
+        NORMALIZE_T(dat9, dat10, exp9, exp10);
+        NORM_COMBINED_S_2(dat9, dat10, exp_comb_5);
+    }
+
+    SECTION("chain tracking")
+    {
+        const char dat1[] = "foo.un";
+        const char dat2[] = "watch.bar ;";
+        const char exp1[] = "var_0000.var_0001";
+        const char exp2[] = "unwatch.bar;";
+        const char exp_comb_1[] = "var_0000.unwatch.bar;";
+
+        const char dat3[] = "foo.un";
+        const char dat4[] = "watch().bar ;";
+        const char exp3[] = "var_0000.var_0001";
+        const char exp4[] = "unwatch().bar;";
+        const char exp_comb_2[] = "var_0000.unwatch().bar;";
+
+        const char dat5[] = "foo['un";
+        const char dat6[] = "watch'].bar ;";
+        const char exp5[] = "var_0000['un";
+        const char exp6[] = "unwatch'].var_0001;";
+        const char exp_comb_3[] = "var_0000['unwatch'].var_0001;";
+
+        const char dat7[] = "foo['un";
+        const char dat8[] = "watch']().bar ;";
+        const char exp7[] = "var_0000['un";
+        const char exp8[] = "unwatch']().var_0001;";
+        const char exp_comb_4[] = "var_0000['unwatch']().var_0001;";
+
+        const char dat9[] = "foo. /*multi";
+        const char dat10[] = "line*/ watch . bar ;";
+        const char exp9[] = "var_0000.";
+        const char exp10[] = "watch.bar;";
+        const char exp_comb_5[] = "var_0000.watch.bar;";
+
+        const char dat11[] = "foo //one";
+        const char dat12[] = "line \n . watch . bar ;";
+        const char exp11[] = "var_0000";
+        const char exp12[] = ".watch.bar;";
+        const char exp_comb_6[] = "var_0000.watch.bar;";
+
+        const char dat13[] = ".";
+        const char dat14[] = "watch ( ) . bar ;";
+        const char exp13[] = ".";
+        const char exp14[] = "watch().bar;";
+        const char exp_comb_7[] = ".watch().bar;";
+
+        const char dat15[] = ".un";
+        const char dat16[] = "watch ( ) . bar ;";
+        const char exp15[] = ".var_0000";
+        const char exp16[] = "unwatch().bar;";
+        const char exp_comb_8[] = ".unwatch().bar;";
+
+        const char dat17[] = "foo.watch ";
+        const char dat18[] = "+ bar ;";
+        const char exp17[] = "var_0000.watch";
+        const char exp18[] = "+var_0001;";
+        const char exp_comb_9[] = "var_0000.watch+var_0001;";
+
+        const char dat19[] = "foo.unwatch ( ) +";
+        const char dat20[] = "bar ;";
+        const char exp19[] = "var_0000.unwatch()+";
+        const char exp20[] = "var_0001;";
+        const char exp_comb_10[] = "var_0000.unwatch()+var_0001;";
+
+        NORMALIZE_T(dat1, dat2, exp1, exp2);
+        NORM_COMBINED_S_2(dat1, dat2, exp_comb_1);
+
+        NORMALIZE_T(dat3, dat4, exp3, exp4);
+        NORM_COMBINED_S_2(dat3, dat4, exp_comb_2);
+
+        NORMALIZE_T(dat5, dat6, exp5, exp6);
+        NORM_COMBINED_S_2(dat5, dat6, exp_comb_3);
+
+        NORMALIZE_T(dat7, dat8, exp7, exp8);
+        NORM_COMBINED_S_2(dat7, dat8, exp_comb_4);
+
+        NORMALIZE_T(dat9, dat10, exp9, exp10);
+        NORM_COMBINED_S_2(dat9, dat10, exp_comb_5);
+
+        NORMALIZE_T(dat11, dat12, exp11, exp12);
+        NORM_COMBINED_S_2(dat11, dat12, exp_comb_6);
+
+        NORMALIZE_T(dat13, dat14, exp13, exp14);
+        NORM_COMBINED_S_2(dat13, dat14, exp_comb_7);
+
+        NORMALIZE_T(dat15, dat16, exp15, exp16);
+        NORM_COMBINED_S_2(dat15, dat16, exp_comb_8);
+
+        NORMALIZE_T(dat17, dat18, exp17, exp18);
+        NORM_COMBINED_S_2(dat17, dat18, exp_comb_9);
+
+        NORMALIZE_T(dat19, dat20, exp19, exp20);
+        NORM_COMBINED_S_2(dat19, dat20, exp_comb_10);
+    }
+
+    SECTION("scope tracking")
+    {
+        const char dat1[] = "foo.(un";
+        const char dat2[] = "watch).bar ;";
+        const char exp1[] = "var_0000.(var_0001";
+        const char exp2[] = "var_0002).var_0003;";
+        const char exp_comb_1[] = "var_0000.(var_0002).var_0003;";
+
+        const char dat3[] = "foo(['un";
+        const char dat4[] = "watch']).bar ;";
+        const char exp3[] = "var_0000(['un";
+        const char exp4[] = "unwatch']).var_0001;";
+        const char exp_comb_2[] = "var_0000(['unwatch']).var_0001;";
+
+        const char dat5[] = "foo.bar(baz.un";
+        const char dat6[] = "watch() . bar ) . foo ;";
+        const char exp5[] = "var_0000.var_0001(var_0002.var_0003";
+        const char exp6[] = "unwatch().bar).var_0000;";
+        const char exp_comb_3[] = "var_0000.var_0001(var_0002.unwatch().bar).var_0000;";
+
+        const char dat7[] = "foo.bar(baz['un";
+        const char dat8[] = "watch']() . bar ) . foo ;";
+        const char exp7[] = "var_0000.var_0001(var_0002['un";
+        const char exp8[] = "unwatch']().var_0001).var_0000;";
+        const char exp_comb_4[] = "var_0000.var_0001(var_0002['unwatch']().var_0001).var_0000;";
+
+        NORMALIZE_T(dat1, dat2, exp1, exp2);
+        NORM_COMBINED_S_2(dat1, dat2, exp_comb_1);
+
+        NORMALIZE_T(dat3, dat4, exp3, exp4);
+        NORM_COMBINED_S_2(dat3, dat4, exp_comb_2);
+
+        NORMALIZE_T(dat5, dat6, exp5, exp6);
+        NORM_COMBINED_S_2(dat5, dat6, exp_comb_3);
+
+        NORMALIZE_T(dat7, dat8, exp7, exp8);
+        NORM_COMBINED_S_2(dat7, dat8, exp_comb_4);
+    }
+}
+
 TEST_CASE("Scope tracking - basic","[JSNormalizer]")
 {
     SECTION("Global only")
@@ -4722,7 +5178,7 @@ TEST_CASE("Scope tracking - error handling", "[JSNormalizer]")
         const char exp[] = "function(){if";
         uint32_t scope_depth = 2;
 
-        JSIdentifierCtx ident_ctx(norm_depth, scope_depth, s_ignored_ids);
+        JSIdentifierCtx ident_ctx(norm_depth, scope_depth, s_ignored_ids, s_ignored_props);
         JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
         auto ret = normalizer.normalize(src, strlen(src));
         std::string dst(normalizer.get_script(), normalizer.script_size());
@@ -4734,8 +5190,8 @@ TEST_CASE("Scope tracking - error handling", "[JSNormalizer]")
 
 TEST_CASE("Function call tracking - basic", "[JSNormalizer]")
 {
-    JSTokenizerTester tester(norm_depth, max_scope_depth, s_ignored_ids, max_template_nesting,
-        max_bracket_depth);
+    JSTokenizerTester tester(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props, 
+        max_template_nesting, max_bracket_depth);
 
     using FuncType = JSTokenizerTester::FuncType;
 
@@ -4784,8 +5240,8 @@ TEST_CASE("Function call tracking - basic", "[JSNormalizer]")
         SECTION("ignored fake defined function identifier")
         {
             const std::unordered_set<std::string> s_ignored_ids_fake {"fake_unescape"};
-            JSTokenizerTester tester_fake(norm_depth, max_scope_depth, s_ignored_ids_fake,
-            max_template_nesting, max_bracket_depth);
+            JSTokenizerTester tester_fake(norm_depth, max_scope_depth, s_ignored_ids_fake, 
+                s_ignored_props, max_template_nesting, max_bracket_depth);
             tester_fake.test_function_scopes({
                 {"fake_unescape(", "fake_unescape(", {FuncType::NOT_FUNC, FuncType::GENERAL}}
             });
@@ -5017,8 +5473,8 @@ TEST_CASE("Function call tracking - basic", "[JSNormalizer]")
 
 TEST_CASE("Function call tracking - nesting", "[JSNormalizer]")
 {
-    JSTokenizerTester tester(norm_depth, max_scope_depth, s_ignored_ids, max_template_nesting,
-        max_bracket_depth);
+    JSTokenizerTester tester(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props,
+        max_template_nesting, max_bracket_depth);
 
     using FuncType = JSTokenizerTester::FuncType;
 
@@ -5109,8 +5565,8 @@ TEST_CASE("Function call tracking - nesting", "[JSNormalizer]")
 
 TEST_CASE("Function call tracking - over multiple PDU", "[JSNormalizer]")
 {
-    JSTokenizerTester tester(norm_depth, max_scope_depth, s_ignored_ids, max_template_nesting,
-        max_bracket_depth);
+    JSTokenizerTester tester(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props,
+        max_template_nesting, max_bracket_depth);
 
     using FuncType = JSTokenizerTester::FuncType;
 
@@ -5543,7 +5999,8 @@ TEST_CASE("JS Normalizer, id normalization", "[JSNormalizer]")
     };
 
     const std::unordered_set<std::string> ids{};
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, ids);
+    const std::unordered_set<std::string> props{};
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, ids, props);
     JSNormalizer normalizer_w_ident(ident_ctx, unlim_depth, max_template_nesting, max_bracket_depth);
 
     REQUIRE(norm_ret(normalizer_w_ident, input) == JSTokenizer::SCRIPT_ENDED);
@@ -5554,7 +6011,8 @@ TEST_CASE("JS Normalizer, id normalization", "[JSNormalizer]")
     };
 
     const std::unordered_set<std::string> ids_n { "n" };
-    JSIdentifierCtx ident_ctx_ids_n(norm_depth, max_scope_depth, ids_n);
+    const std::unordered_set<std::string> props_n { "n" };
+    JSIdentifierCtx ident_ctx_ids_n(norm_depth, max_scope_depth, ids_n, props_n);
     JSNormalizer normalizer_iids(ident_ctx_ids_n, unlim_depth,
         max_template_nesting, max_bracket_depth);
 
@@ -5640,7 +6098,7 @@ TEST_CASE("JS Normalizer, unescape", "[JSNormalizer]")
     const char* src_f_unescape = f_unescape.c_str();
     size_t src_len = norm_depth;
 
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer norm(ident_ctx, unlim_depth, max_template_nesting, norm_depth);
 
     REQUIRE(norm_ret(norm, str_unescape) == JSTokenizer::SCRIPT_ENDED);
index cd871d43d42da8b665c1647e9453e4204f0106b6..e20dee2474884435bdcf5d7d01495ca2b974a3fa 100644 (file)
@@ -71,7 +71,7 @@ void test_scope(const char* context, const std::list<JSProgramScopeType>& stack)
 {
     std::string buf(context);
     buf += "</script>";
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
     normalizer.normalize(buf.c_str(), buf.size());
     CHECK(ident_ctx.get_types() == stack);
@@ -79,7 +79,7 @@ void test_scope(const char* context, const std::list<JSProgramScopeType>& stack)
 
 void test_normalization(const char* source, const char* expected)
 {
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
     normalizer.normalize(source, strlen(source));
     std::string result_buf(normalizer.get_script(), normalizer.script_size());
@@ -88,7 +88,7 @@ void test_normalization(const char* source, const char* expected)
 
 void test_normalization_bad(const char* source, const char* expected, JSTokenizer::JSRet eret)
 {
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
     auto ret = normalizer.normalize(source, strlen(source));
     std::string result_buf(normalizer.get_script(), normalizer.script_size());
@@ -98,7 +98,7 @@ void test_normalization_bad(const char* source, const char* expected, JSTokenize
 
 void test_normalization_mixed_encoding(const char* source, const char* expected)
 {
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
     auto ret = normalizer.normalize(source, strlen(source));
     std::string result_buf(normalizer.get_script(), normalizer.script_size());
@@ -109,7 +109,7 @@ void test_normalization_mixed_encoding(const char* source, const char* expected)
 
 void test_normalization(const std::vector<PduCase>& pdus)
 {
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
 
     for (const auto& pdu : pdus)
@@ -124,7 +124,7 @@ void test_normalization(const std::vector<PduCase>& pdus)
 
 void test_normalization(const std::list<ScopedPduCase>& pdus)
 {
-    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids);
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ignored_ids, s_ignored_props);
     JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
     for (auto pdu:pdus)
     {
index 269fabbb627de19abfefa809362432bed2d93d0c..23a136edc30605e04d526ef23f0a675fcf9fa265 100644 (file)
@@ -35,7 +35,12 @@ constexpr int max_template_nesting = 4;
 constexpr int max_bracket_depth = 256;
 constexpr int max_scope_depth = 256;
 static const std::unordered_set<std::string> s_ignored_ids {
-    "console", "eval", "document", "unescape", "decodeURI", "decodeURIComponent", "String"
+    "console", "eval", "document", "unescape", "decodeURI", "decodeURIComponent", "String",
+    "name", "u"
+};
+
+static const std::unordered_set<std::string> s_ignored_props {
+    "watch", "unwatch", "split", "reverse", "join", "name", "w"
 };
 
 namespace snort
@@ -49,7 +54,7 @@ class JSIdentifierCtxStub : public JSIdentifierCtxBase
 public:
     JSIdentifierCtxStub() = default;
 
-    const char* substitute(const char* identifier) override
+    const char* substitute(const char* identifier, bool) override
     { return identifier; }
     virtual void add_alias(const char*, const std::string&&) override {}
     virtual const char* alias_lookup(const char* alias) const override
@@ -67,9 +72,10 @@ class JSTokenizerTester
 public:
     JSTokenizerTester(int32_t depth, uint32_t max_scope_depth,
         const std::unordered_set<std::string>& ignored_ids,
+        const std::unordered_set<std::string>& ignored_props,
         uint8_t max_template_nesting, uint32_t max_bracket_depth)
         :
-        ident_ctx(depth, max_scope_depth, ignored_ids),
+        ident_ctx(depth, max_scope_depth, ignored_ids, ignored_props),
         normalizer(ident_ctx, depth, max_template_nesting, max_bracket_depth)
     { }