From 2ccb7186a2e0c6be5f21fac9b5580c86ed1a4033 Mon Sep 17 00:00:00 2001 From: User4martin Date: Tue, 11 Aug 2015 16:21:37 +0100 Subject: [PATCH 1/4] Fix parsing Issue 63 https://github.com/3rd-Eden/useragent/issues/63 --- index.js | 39 +++++++++++++++++++++++++++------------ lib/update.js | 44 ++++++++++++++++++++------------------------ 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/index.js b/index.js index 1d520ad..f3b23dc 100644 --- a/index.js +++ b/index.js @@ -39,6 +39,21 @@ function Agent(family, major, minor, patch, source) { this.source = source || ''; } +function replaceRegExResult(matches, templates) { + var r = []; + var m = matches.map(function(v){ v = typeof v === 'string' ? v.replace(/\&/g, '&&').replace(/\$/g, '&D') : v; }); + for(var t=1; t<=4; t++) { // template[1..4] + r[t] = templates[t]; + if (typeof r[t] === 'string') { + for(var i=1; i<=9; i++) { // $1..$9 + r[t] = r[t].replace(new RegExp('\\$'+i, 'g'), matches[i] != undefined ? matches[i] : ''); + } + r[t] = r[t].replace(/&D/g, '$').replace(/&&/g, '&'); + } + } + return r; +} + /** * OnDemand parsing of the Operating System. * @@ -58,7 +73,7 @@ Object.defineProperty(Agent.prototype, 'os', { if (res = parsers[i][0].exec(userAgent)) { parser = parsers[i]; - if (parser[1]) res[1] = parser[1].replace('$1', res[1]); + res = replaceRegExResult(res, parser); break; } } @@ -68,9 +83,9 @@ Object.defineProperty(Agent.prototype, 'os', { ? new OperatingSystem() : new OperatingSystem( res[1] - , parser[2] || res[2] - , parser[3] || res[3] - , parser[4] || res[4] + , res[2] + , res[3] + , res[4] ) }).os; }, @@ -109,7 +124,7 @@ Object.defineProperty(Agent.prototype, 'device', { if (res = parsers[i][0].exec(userAgent)) { parser = parsers[i]; - if (parser[1]) res[1] = parser[1].replace('$1', res[1]); + res = replaceRegExResult(res, parser); break; } } @@ -119,9 +134,9 @@ Object.defineProperty(Agent.prototype, 'device', { ? new Device() : new Device( res[1] - , parser[2] || res[2] - , parser[3] || res[3] - , parser[4] || res[4] + , res[2] + , res[3] + , res[4] ) }).device; }, @@ -427,12 +442,12 @@ exports.parse = function parse(userAgent, jsAgent) { if (res = parsers[i][0].exec(userAgent)) { parser = parsers[i]; - if (parser[1]) res[1] = parser[1].replace('$1', res[1]); + res = replaceRegExResult(res, parser); if (!jsAgent) return new Agent( res[1] - , parser[2] || res[2] - , parser[3] || res[3] - , parser[4] || res[4] + , res[2] + , res[3] + , res[4] , userAgent ); diff --git a/lib/update.js b/lib/update.js index e18ca9d..18abd99 100644 --- a/lib/update.js +++ b/lib/update.js @@ -50,6 +50,16 @@ exports.update = function update(callback) { }); }; +function getReplacement(field, resource, fallback) { + if (! field) + return fallback; + if (Array.isArray(field)) + field = field.filter(function(f){ return resource[f] })[0]; + if (resource[field] != undefined) + return resource[field]; + return fallback; +} + /** * Parse the given sources. * @@ -93,17 +103,17 @@ exports.parse = function parse(sources, callback) { [ { resource: 'user_agent_parsers' - , replacement: 'family_replacement' + , replacement: ['family_replacement', 'v1_replacement', 'v2_replacement', 'v3_replacement' ] , name: 'browser' } , { resource: 'device_parsers' - , replacement: 'device_replacement' + , replacement: [['brand_replacement', 'device_replacement' ], 'model_replacement'] // , name: 'device' } , { resource: 'os_parsers' - , replacement: 'os_replacement' + , replacement: ['os_replacement', 'os_v1_replacement', 'os_v2_replacement' ] , name: 'os' } ].forEach(function parsing(details) { @@ -120,39 +130,25 @@ exports.parse = function parse(sources, callback) { // We need to JSON stringify the data to properly add slashes escape other // kinds of crap in the RegularExpression. If we don't do thing we get // some illegal token warnings. + var rflags = resource.regex_flags || ''; + if (rflags) rflags = ", '" + rflags + "'"; parser = 'parser = Object.create(null);\n'; - parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + ');\n'; + parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + rflags + ');\n'; // Check if we have replacement for the parsed family name - if (resource[details.replacement]) { - parser += 'parser[1] = "'+ resource[details.replacement].replace('"', '\\"') +'";'; - } else { - parser += 'parser[1] = 0;'; - } + parser += 'parser[1] = "'+ getReplacement(details.replacement[0], resource, '$1').replace('"', '\\"') +'";'; parser += '\n'; - if (resource.v1_replacement) { - parser += 'parser[2] = "'+ resource.v1_replacement.replace('"', '\\"') +'";'; - } else { - parser += 'parser[2] = 0;'; - } + parser += 'parser[2] = "'+ getReplacement(details.replacement[1], resource, '$2').replace('"', '\\"') +'";'; parser += '\n'; - if (resource.v2_replacement) { - parser += 'parser[3] = "'+ resource.v2_replacement.replace('"', '\\"') +'";'; - } else { - parser += 'parser[3] = 0;'; - } + parser += 'parser[3] = "'+ getReplacement(details.replacement[2], resource, '$3').replace('"', '\\"') +'";'; parser += '\n'; - if (resource.v3_replacement) { - parser += 'parser[4] = "'+ resource.v3_replacement.replace('"', '\\"') +'";'; - } else { - parser += 'parser[4] = 0;'; - } + parser += 'parser[4] = "'+ getReplacement(details.replacement[3], resource, '$4').replace('"', '\\"') +'";'; parser += '\n'; parser += 'exports.'+ details.name +'['+ i +'] = parser;'; From 48be96f82e9efe82c6bd0e051cb88d372171f0cf Mon Sep 17 00:00:00 2001 From: User4martin Date: Tue, 11 Aug 2015 18:59:47 +0100 Subject: [PATCH 2/4] fix regex_flag --- lib/update.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/update.js b/lib/update.js index 18abd99..817a0c4 100644 --- a/lib/update.js +++ b/lib/update.js @@ -130,7 +130,7 @@ exports.parse = function parse(sources, callback) { // We need to JSON stringify the data to properly add slashes escape other // kinds of crap in the RegularExpression. If we don't do thing we get // some illegal token warnings. - var rflags = resource.regex_flags || ''; + var rflags = resource.regex_flag || ''; if (rflags) rflags = ", '" + rflags + "'"; parser = 'parser = Object.create(null);\n'; parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + rflags + ');\n'; @@ -138,7 +138,7 @@ exports.parse = function parse(sources, callback) { // Check if we have replacement for the parsed family name parser += 'parser[1] = "'+ getReplacement(details.replacement[0], resource, '$1').replace('"', '\\"') +'";'; - parser += '\n'; + parser += '\n'; parser += 'parser[2] = "'+ getReplacement(details.replacement[1], resource, '$2').replace('"', '\\"') +'";'; From da66fbe56dcfd013013175c967ae1385ba10eb82 Mon Sep 17 00:00:00 2001 From: User4martin Date: Tue, 11 Aug 2015 19:44:52 +0100 Subject: [PATCH 3/4] Allow setting values for unknown family or version --- index.js | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index f3b23dc..25c8ae6 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,10 @@ var agentparsers = regexps.browser var deviceparsers = regexps.device , deviceparserslength = deviceparsers.length; +// Default for unknown version +var UnknownFamily = 'Other'; +var UnknownVersion = '0'; + /** * The representation of a parsed user agent. * @@ -32,10 +36,10 @@ var deviceparsers = regexps.device * @api public */ function Agent(family, major, minor, patch, source) { - this.family = family || 'Other'; - this.major = major || '0'; - this.minor = minor || '0'; - this.patch = patch || '0'; + this.family = family || UnknownFamily; + this.major = major || UnknownVersion; + this.minor = minor || UnknownVersion; + this.patch = patch || UnknownVersion; this.source = source || ''; } @@ -176,7 +180,7 @@ Agent.prototype.toAgent = function toAgent() { */ Agent.prototype.toString = function toString() { var agent = this.toAgent() - , os = this.os !== 'Other' ? this.os : false; + , os = this.os !== UnknownFamily ? this.os : false; return agent + (os ? ' / ' + os : ''); }; @@ -235,10 +239,10 @@ Agent.prototype.toJSON = function toJSON() { * @api public */ function OperatingSystem(family, major, minor, patch) { - this.family = family || 'Other'; - this.major = major || '0'; - this.minor = minor || '0'; - this.patch = patch || '0'; + this.family = family || UnknownFamily; + this.major = major || UnknownVersion; + this.minor = minor || UnknownVersion; + this.patch = patch || UnknownVersion; } /** @@ -308,10 +312,10 @@ OperatingSystem.prototype.toJSON = function toJSON(){ * @api public */ function Device(family, major, minor, patch) { - this.family = family || 'Other'; - this.major = major || '0'; - this.minor = minor || '0'; - this.patch = patch || '0'; + this.family = family || UnknownFamily; + this.major = major || UnknownVersion; + this.minor = minor || UnknownVersion; + this.patch = patch || UnknownVersion; } /** @@ -599,6 +603,15 @@ exports.fromJSON = function fromJSON(details) { return agent; }; + +exports.setUnknownFamilyString = function setUnknownFamilyString(s) { + UnknownFamily = s; +} + +exports.setUnknownVersionString = function setUnknownVersionString(s) { + UnknownVersion = s; +} + /** * Library version. * From 2d7a45ea68ef54ae5d1781c229ef9a9bd6dc0cfd Mon Sep 17 00:00:00 2001 From: User4martin Date: Tue, 11 Aug 2015 21:44:44 +0100 Subject: [PATCH 4/4] check for duplicate replacements / optimize replacements in found result --- index.js | 7 ++++--- lib/update.js | 47 ++++++++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 25c8ae6..c6ca2a0 100644 --- a/index.js +++ b/index.js @@ -47,11 +47,12 @@ function replaceRegExResult(matches, templates) { var r = []; var m = matches.map(function(v){ v = typeof v === 'string' ? v.replace(/\&/g, '&&').replace(/\$/g, '&D') : v; }); for(var t=1; t<=4; t++) { // template[1..4] - r[t] = templates[t]; + var tmpl = templates[t]; + r[t] = tmpl[0]; if (typeof r[t] === 'string') { - for(var i=1; i<=9; i++) { // $1..$9 + tmpl[1].forEach(function(i){ r[t] = r[t].replace(new RegExp('\\$'+i, 'g'), matches[i] != undefined ? matches[i] : ''); - } + }); r[t] = r[t].replace(/&D/g, '$').replace(/&&/g, '&'); } } diff --git a/lib/update.js b/lib/update.js index 817a0c4..8ab3fb5 100644 --- a/lib/update.js +++ b/lib/update.js @@ -50,14 +50,33 @@ exports.update = function update(callback) { }); }; -function getReplacement(field, resource, fallback) { +function getReplacement(field, resource, fallback, lastRepl) { + lastRepl = lastRepl || {}; + var usedMap = lastRepl.usedMap || {}; + + var used = []; + if (usedMap[fallback]) { + fallback = ''; + } else { + used = [fallback.replace('$', '')]; + } + if (! field) - return fallback; + return { text: fallback, used: used, usedMap: usedMap }; if (Array.isArray(field)) field = field.filter(function(f){ return resource[f] })[0]; - if (resource[field] != undefined) - return resource[field]; - return fallback; + if (! field) + return { text: fallback, used: used, usedMap: usedMap }; + if (resource[field] == undefined) + return { text: fallback, used: used, usedMap: usedMap }; + + var t = resource[field]; + var d = {}; + var m = t.match(/\$\d/g); + if (m) m.forEach(function(m) { usedMap[m] = true; m = m.replace('$',''); d[m] = true; }); + used = Object.keys(d).sort(function(a,b){ return a-b; }); + + return { text: t, used: used, usedMap: usedMap }; } /** @@ -136,21 +155,15 @@ exports.parse = function parse(sources, callback) { parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + rflags + ');\n'; // Check if we have replacement for the parsed family name - parser += 'parser[1] = "'+ getReplacement(details.replacement[0], resource, '$1').replace('"', '\\"') +'";'; + var r = {}; + for(var j = 0; j < 4; j++) { + r = getReplacement(details.replacement[j], resource, '$'+(j+1), r); + var used = r.used.join(', '); + parser += 'parser[' + (j+1) + '] = ["'+ r.text.replace('"', '\\"') +'", [' + used + ']];'; parser += '\n'; + } - parser += 'parser[2] = "'+ getReplacement(details.replacement[1], resource, '$2').replace('"', '\\"') +'";'; - - parser += '\n'; - - parser += 'parser[3] = "'+ getReplacement(details.replacement[2], resource, '$3').replace('"', '\\"') +'";'; - - parser += '\n'; - - parser += 'parser[4] = "'+ getReplacement(details.replacement[3], resource, '$4').replace('"', '\\"') +'";'; - - parser += '\n'; parser += 'exports.'+ details.name +'['+ i +'] = parser;'; results[details.resource].push(parser); }