Skip to content

Commit

Permalink
feat: supports CLIENTTAGDENY parsing and "is blocked" function
Browse files Browse the repository at this point in the history
This introduces a `MessageTags.parseDenylist` which parses
the value of `CLIENTTAGDENY=<value>` 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.
  • Loading branch information
RaitoBezarius committed Aug 12, 2023
1 parent 117580e commit bb168ef
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/messagetags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
'\\\\': '\\',
Expand Down Expand Up @@ -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);
}
}
43 changes: 43 additions & 0 deletions test/messagetags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down

0 comments on commit bb168ef

Please sign in to comment.