diff --git a/.gitignore b/.gitignore index 3364eab..6451580 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ lib/*.map lib/api-functions.js lib/auth.js -lib/common.js lib/config.js lib/cwmp.js lib/db.js diff --git a/lib/common.coffee b/lib/common.coffee deleted file mode 100644 index 9362a94..0000000 --- a/lib/common.coffee +++ /dev/null @@ -1,229 +0,0 @@ -### -# Copyright 2013-2017 Zaid Abdulla -# -# This file is part of GenieACS. -# -# GenieACS is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# GenieACS is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with GenieACS. If not, see . -### - -buffer = require 'buffer' - -UNDEFINED_TYPE = '[object Undefined]' -NULL_TYPE = '[object Null]' -BOOLEAN_TYPE = '[object Boolean]' -NUMBER_TYPE = '[object Number]' -STRING_TYPE = '[object String]' -ARRAY_TYPE = '[object Array]' -OBJECT_TYPE = '[object Object]' -REGEXP_TYPE = '[object RegExp]' -DATE_TYPE = '[object Date]' - - -typeOf = (obj) -> - Object.prototype.toString.call(obj) - - -exports.generateDeviceId = (deviceIdStruct) -> - # Percent escaping function. Escapes everything except alphanumerics and underscore - esc = (str) -> - str.replace(/[^A-Za-z0-9_]/g, (chr) -> - buf = new buffer.Buffer(chr, 'utf8') - rep = '' - rep += "%#{b.toString(16).toUpperCase()}" for b in buf - return rep - ) - - # Guaranteeing globally unique id as defined in TR-069 - if deviceIdStruct['ProductClass'] - return "#{esc(deviceIdStruct['OUI'])}-#{esc(deviceIdStruct['ProductClass'])}-#{esc(deviceIdStruct['SerialNumber'])}" - - return "#{esc(deviceIdStruct['OUI'])}-#{esc(deviceIdStruct['SerialNumber'])}" - - -exports.getParamValueFromPath = (obj, path) -> - pp = path.split('.') - ref = obj - try - for p in pp - ref = ref[p] - return ref - catch err - return undefined - - -pathOverlap = (a, b, start) -> - if a.length == b.length - res = 3 - else if a.length > b.length - res = 2 - else - res = 1 - - for i in [(start ? 0)...Math.min(a.length, b.length)] by 1 - if a[i]? - if not b[i]? - res &= 2 - else if a[i] != b[i] - return 0 - else if b[i]? - res &= 1 - - return 0 if not res - - return res - - -parseAlias = (pattern, start, res) -> - aliases = [] - i = start - while i < pattern.length and pattern[i] != ']' - alias = [] - i = j = parsePath(pattern, i, alias) + 1 - - while pattern[j] not in [']', ','] - if pattern[j] == '"' and i == j - ++ j - while pattern[j] != '"' or pattern[j - 1] == '\\' - if ++ j >= pattern.length - throw new Error('Invalid alias expression') - if ++ j >= pattern.length - throw new Error('Invalid alias expression') - value = pattern[i...j].trim() - i = j - if value[0] == '"' - try - value = JSON.parse(value) - catch err - throw new Error('Invalid alias expression') - alias.push(value) - aliases.push(alias) - ++ i if pattern[i] == ',' - - # Need to sort to ensure identical expressions have idential string representation - srt = (a, b) -> - for j in [0...Math.min(a.length, b.length)] by 2 - for k in [0...Math.min(a[j].length, b[j].length)] by 1 - if Array.isArray(a[j][k]) - if Array.isArray(b[j][k]) - return srt(a[j][k], b[j][k]) - else if not b[j][k]? - return -1 - else - return 1 - else if not a[j][k]? - if not b[j][k]? - return 0 - else - return 1 - else if not b[j][k]? or Array.isArray(b[j][k]) - return -1 - else if a[j][k] > b[j][k] - return 1 - else if a[j][k] < b[j][k] - return -1 - - if a[j].length > b[j].length - return -1 - else if a[j].length < b[j].length - return 1 - - if a[j + 1] > b[j + 1] - return -1 - else if a[j + 1] < b[j + 1] - return 1 - - if a.length > b.length - return -1 - else if a.length < b.length - return 1 - - return 0 - - aliases.sort(srt) - res.push([].concat.apply([], aliases)) - return i - - -parsePath = (pattern, start, res) -> - path = [] - path.wildcard = 0 - path.alias = 0 - i = start ? 0 - - # Colon separator is needed for parseAlias - if i < pattern.length and pattern[i] != ':' - while true - if pattern[i] == '[' - path.alias |= 1 << path.length - i = parseAlias(pattern, i + 1, path) + 1 - else - j = i - while i < pattern.length and pattern[i] != ':' and pattern[i] != '.' - ++ i - n = pattern.slice(j, i) - path.wildcard |= 1 << path.length if n == '*' - path.push(n) - - if i >= pattern.length or pattern[i] == ':' - break - else if pattern[i] != '.' - throw new Error('Invalid alias expression') - - ++ i - - Object.freeze(path) - - if not res? - return path - - res.push(path) - return i - - -addPathMeta = (path) -> - return path if path.alias? or path.wildcard? - - path.alias = 0 - path.wildcard = 0 - - for p, i in path - if typeOf(p) is ARRAY_TYPE - path.alias |= 1 << i - for j in [0...p.length] by 2 - addPathMeta(p[j]) - else if p == '*' - path.wildcard |= 1 << i - - Object.freeze(path) - return path - - -# Source: http://stackoverflow.com/a/6969486 -escapeRegExp = (str) -> - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") - - -exports.UNDEFINED_TYPE = UNDEFINED_TYPE -exports.NULL_TYPE = NULL_TYPE -exports.NUMBER_TYPE = NUMBER_TYPE -exports.STRING_TYPE = STRING_TYPE -exports.ARRAY_TYPE = ARRAY_TYPE -exports.OBJECT_TYPE = OBJECT_TYPE -exports.REGEXP_TYPE = REGEXP_TYPE - -exports.typeOf = typeOf -exports.pathOverlap = pathOverlap -exports.parsePath = parsePath -exports.addPathMeta = addPathMeta -exports.escapeRegExp = escapeRegExp diff --git a/lib/common.js b/lib/common.js new file mode 100644 index 0000000..05f9e96 --- /dev/null +++ b/lib/common.js @@ -0,0 +1,217 @@ +/** + * Copyright 2013-2018 Zaid Abdulla + * + * This file is part of GenieACS. + * + * GenieACS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * GenieACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GenieACS. If not, see . + */ +"use strict"; + +const UNDEFINED_TYPE = "[object Undefined]"; +const NULL_TYPE = "[object Null]"; +const BOOLEAN_TYPE = "[object Boolean]"; +const NUMBER_TYPE = "[object Number]"; +const STRING_TYPE = "[object String]"; +const ARRAY_TYPE = "[object Array]"; +const OBJECT_TYPE = "[object Object]"; +const REGEXP_TYPE = "[object RegExp]"; +const DATE_TYPE = "[object Date]"; + +const typeOf = (obj) => Object.prototype.toString.call(obj); + +function generateDeviceId(deviceIdStruct) { + // Escapes everything except alphanumerics and underscore + function esc(str) { + return str.replace(/[^A-Za-z0-9_]/g, (chr) => { + const buf = new Buffer(chr, "utf8"); + let rep = ""; + for (let b of buf) + rep += "%" + (b.toString(16).toUpperCase()); + return rep; + }); + } + + // Guaranteeing globally unique id as defined in TR-069 + if (deviceIdStruct["ProductClass"]) + return (esc(deviceIdStruct["OUI"])) + "-" + (esc(deviceIdStruct["ProductClass"])) + "-" + (esc(deviceIdStruct["SerialNumber"])); + return (esc(deviceIdStruct["OUI"])) + "-" + (esc(deviceIdStruct["SerialNumber"])); +} + +function parseAlias(pattern, start, res) { + const aliases = []; + let i = start; + while (i < pattern.length && pattern[i] !== "]") { + const alias = []; + let j = i = parsePath(pattern, i, alias) + 1; + while (pattern[j] !== "]" && pattern[j] !== ",") { + if (pattern[j] === "\"" && i === j) { + ++j; + while (pattern[j] !== "\"" || pattern[j - 1] === "\\") { + if (++j >= pattern.length) + throw new Error("Invalid alias expression"); + } + } + if (++j >= pattern.length) + throw new Error("Invalid alias expression"); + } + + let value = pattern.slice(i, j).trim(); + i = j; + if (value[0] === "\"") { + try { + value = JSON.parse(value); + } catch (error) { + throw new Error("Invalid alias expression"); + } + } + + alias.push(value); + aliases.push(alias); + if (pattern[i] === ",") + ++i; + } + + // Sort to ensure identical expressions have idential string representation + function srt(a, b) { + const jMax = Math.min(a.length, b.length); + for (let j = 0; j < jMax; j += 2) { + const kMax = Math.min(a[j].length, b[j].length); + for (let k = 0; k < kMax; ++ k) { + if (Array.isArray(a[j][k])) { + if (Array.isArray(b[j][k])) + return srt(a[j][k], b[j][k]); + else if (b[j][k] == null) + return -1; + else + return 1; + } else if (a[j][k] == null) { + if (b[j][k] == null) + return 0; + else + return 1; + } else if ((b[j][k] == null) || Array.isArray(b[j][k])) { + return -1; + } else if (a[j][k] > b[j][k]) { + return 1; + } else if (a[j][k] < b[j][k]) { + return -1; + } + } + + if (a[j].length > b[j].length) + return -1; + else if (a[j].length < b[j].length) + return 1; + + if (a[j + 1] > b[j + 1]) + return -1; + else if (a[j + 1] < b[j + 1]) + return 1; + } + + if (a.length > b.length) + return -1; + else if (a.length < b.length) + return 1; + + return 0; + } + + aliases.sort(srt); + res.push([].concat.apply([], aliases)); + return i; +} + +function parsePath(pattern, start, res) { + const path = []; + path.wildcard = 0; + path.alias = 0; + let i = start || 0; + + // Colon separator is needed for parseAlias + if (i < pattern.length && pattern[i] !== ":") { + while (true) { + if (pattern[i] === "[") { + path.alias |= 1 << path.length; + i = parseAlias(pattern, i + 1, path) + 1; + } else { + let j = i; + while (i < pattern.length && pattern[i] !== ":" && pattern[i] !== ".") + ++i; + + const n = pattern.slice(j, i); + if (n === "*") + path.wildcard |= 1 << path.length; + path.push(n); + } + + if (i >= pattern.length || pattern[i] === ":") + break; + else if (pattern[i] !== ".") + throw new Error("Invalid alias expression"); + + ++i; + } + } + + Object.freeze(path); + + if (res == null) + return path; + + res.push(path); + return i; +} + +function addPathMeta(path) { + if (path.alias != null && path.wildcard != null) + return path; + + path.alias = 0; + path.wildcard = 0; + + for (let [i, p] of path.entries()) { + if (Array.isArray(p)) { + path.alias |= 1 << i; + for (let j = 0; j < p.length; j += 2) + addPathMeta(p[j]); + } else if (p === "*") { + path.wildcard |= 1 << i; + } + } + + Object.freeze(path); + return path; +} + +// Source: http://stackoverflow.com/a/6969486 +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +} + +exports.UNDEFINED_TYPE = UNDEFINED_TYPE; +exports.NULL_TYPE = NULL_TYPE; +exports.BOOLEAN_TYPE = BOOLEAN_TYPE; +exports.NUMBER_TYPE = NUMBER_TYPE; +exports.STRING_TYPE = STRING_TYPE; +exports.ARRAY_TYPE = ARRAY_TYPE; +exports.OBJECT_TYPE = OBJECT_TYPE; +exports.DATE_TYPE = DATE_TYPE; +exports.REGEXP_TYPE = REGEXP_TYPE; + +exports.typeOf = typeOf; +exports.generateDeviceId = generateDeviceId; +exports.parsePath = parsePath; +exports.addPathMeta = addPathMeta; +exports.escapeRegExp = escapeRegExp;