From bb168ef9957bcdee5eefe6a7646e860afbcf0711 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 12 Aug 2023 12:26:45 +0200 Subject: [PATCH] feat: supports CLIENTTAGDENY parsing and "is blocked" function This introduces a `MessageTags.parseDenylist` which parses the value of `CLIENTTAGDENY=` string into a structure containing everything you need to perform O(#allowed tags) or O(#denied tags) check for whether a tag is allowed or not. Then, a `MessageTags.isBlocked` function can be used to leverage that structure to perform this check. --- src/messagetags.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/messagetags.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/messagetags.js b/src/messagetags.js index ae78cc15..ecb34f2f 100644 --- a/src/messagetags.js +++ b/src/messagetags.js @@ -6,6 +6,8 @@ module.exports.decodeValue = decodeValue; module.exports.encodeValue = encodeValue; module.exports.decode = decode; module.exports.encode = encode; +module.exports.parseDenylist = parseDenylist; +module.exports.isBlocked = isBlocked; const tokens_map = { '\\\\': '\\', @@ -68,3 +70,45 @@ function encode(tags, separator = ';') { return parts.join(separator); } + +// Parses a raw CLIENTTAGDENY= denylist +// into a { allBlockedByDefault: boolean, explicitlyAccepted: string[], explicitlyDenied: string[] } +// structure. +function parseDenylist(raw) { + const denylist = { + allBlockedByDefault: false, + explicitlyAccepted: [], + explicitlyDenied: [] + }; + const parts = raw.split(','); + + for (let idx = 0; idx < parts.length; idx++) { + const tag = parts[idx]; + if (tag === '') { + continue; + } + + if (tag === '*') { + denylist.allBlockedByDefault = true; + continue; + } + + if (tag[0] === '-') { + denylist.explicitlyAccepted.push(tag.slice(1)); + } else { + denylist.explicitlyDenied.push(tag); + } + } + + return denylist; +} + +// Takes a parsed denylist and returns whether tag is allowed +// according to current denial policies. +function isBlocked(denylist, tag) { + if (denylist.allBlockedByDefault) { + return !denylist.explicitlyAccepted.includes(tag); + } else { + return denylist.explicitlyDenied.includes(tag); + } +} diff --git a/test/messagetags.js b/test/messagetags.js index 745d2af4..597b4aae 100644 --- a/test/messagetags.js +++ b/test/messagetags.js @@ -8,6 +8,49 @@ const assert = chai.assert; chai.use(require('chai-subset')); describe('src/messagetags.js', function() { + describe('CLIENTTAGDENY= parsing', function() { + it('should parse CLIENTTAGDENY=', function() { + assert.deepEqual(MessageTags.parseDenylist(''), { + allBlockedByDefault: false, + explicitlyDenied: [], + explicitlyAccepted: [] + }); + }); + + it('should parse CLIENTTAGDENY=*,-a', function() { + assert.deepEqual(MessageTags.parseDenylist('*,-a'), { + allBlockedByDefault: true, + explicitlyAccepted: ['a'], + explicitlyDenied: [] + }); + }); + + it('should parse CLIENTTAGDENY=a,b', function() { + assert.deepEqual(MessageTags.parseDenylist('a,b'), { + allBlockedByDefault: false, + explicitlyAccepted: [], + explicitlyDenied: ['a', 'b'] + }); + }); + }); + + describe('CLIENTTAGDENY= logic', function() { + it('should block all tags (`b`) with * and no exception', function() { + assert.isTrue(MessageTags.isBlocked(MessageTags.parseDenylist('*'), 'b')); + }); + + it('should not block all tags with * and exceptions (`c`, `a`)', function() { + assert.isFalse(MessageTags.isBlocked(MessageTags.parseDenylist('*,-c,-a'), 'a')); + assert.isFalse(MessageTags.isBlocked(MessageTags.parseDenylist('*,-c,-a'), 'c')); + assert.isTrue(MessageTags.isBlocked(MessageTags.parseDenylist('*,-c,-a'), 'b')); + }); + + it('should block a specific tag if no * is present', function() { + assert.isTrue(MessageTags.isBlocked(MessageTags.parseDenylist('a'), 'a')); + assert.isFalse(MessageTags.isBlocked(MessageTags.parseDenylist('a'), 'b')); + }); + }); + describe('value encoding', function() { it('should decode characters to correct strings', function() { const plain = "Some people use IRC; others don't \\o/ Note: Use IRC\r\n";