diff --git a/lib/irc.js b/lib/irc.js index 16fbee6..b540051 100644 --- a/lib/irc.js +++ b/lib/irc.js @@ -320,6 +320,7 @@ export function parseCTCP(msg) { } export function parseISUPPORT(tokens, params) { + var changed = []; tokens.forEach((tok) => { if (tok.startsWith("-")) { var k = tok.slice(1); @@ -333,6 +334,168 @@ export function parseISUPPORT(tokens, params) { k = tok.slice(0, i); v = tok.slice(i + 1); } - params.set(k.toUpperCase(), v); + + k = k.toUpperCase(); + + params.set(k, v); + changed.push(k); + }); + return changed; +} + +export const CaseMapping = { + ASCII(str) { + var out = ""; + for (var i = 0; i < str.length; i++) { + var ch = str[i]; + if ("A" <= ch && ch <= "Z") { + ch = ch.toLowerCase(); + } + out += ch; + } + return out; + }, + + RFC1459(str) { + var out = ""; + for (var i = 0; i < str.length; i++) { + var ch = str[i]; + if ("A" <= ch && ch <= "Z") { + ch = ch.toLowerCase(); + } else if (ch == "{") { + ch = "["; + } else if (ch == "}") { + ch = "]"; + } else if (ch == "\\") { + ch = "|"; + } else if (ch == "~") { + ch = "^"; + } + out += ch; + } + return out; + }, + + RFC1459Strict(str) { + var out = ""; + for (var i = 0; i < str.length; i++) { + var ch = str[i]; + if ("A" <= ch && ch <= "Z") { + ch = ch.toLowerCase(); + } else if (ch == "{") { + ch = "["; + } else if (ch == "}") { + ch = "]"; + } else if (ch == "\\") { + ch = "|"; + } + out += ch; + } + return out; + }, + + byName(name) { + switch (name) { + case "ascii": + return CaseMapping.ASCII; + case "rfc1459": + return CaseMapping.RFC1459; + case "rfc1459-strict": + return CaseMapping.RFC1459Strict; + } + return null; + }, +}; + +function createIterator(next) { + var it = { next }; + // Not defining this can lead to surprises when feeding the iterator + // to e.g. Array.from + it[Symbol.iterator] = () => it; + return it; +} + +function mapIterator(it, f) { + return createIterator(() => { + var { value, done } = it.next(); + if (done) { + return { done: true }; + } + return { value: f(value), done: false }; }); } + +export class CaseMapMap { + caseMap = null; + map = null; + + constructor(iterable, cm) { + if ((iterable instanceof CaseMapMap) && (iterable.caseMap === cm || !cm)) { + // Fast-path if we're just cloning another CaseMapMap + this.caseMap = iterable.caseMap; + this.map = new Map(iterable.map); + } else { + if (!cm) { + throw new Error("Missing case-mapping when creating CaseMapMap"); + } + + this.caseMap = cm; + this.map = new Map(); + + if (iterable) { + for (var [key, value] of iterable) { + this.set(key, value); + } + } + } + } + + get size() { + return this.map.size; + } + + has(key) { + return this.map.has(this.caseMap(key)); + } + + get(key) { + var kv = this.map.get(this.caseMap(key)); + if (kv) { + return kv.value; + } + return undefined; + } + + set(key, value) { + this.map.set(this.caseMap(key), { key, value }); + } + + delete(key) { + this.map.delete(this.caseMap(key)); + } + + entries() { + var it = this.map.values(); + return mapIterator(it, (kv) => { + return [kv.key, kv.value]; + }); + } + + keys() { + var it = this.map.values(); + return mapIterator(it, (kv) => { + return kv.key; + }); + } + + values() { + var it = this.map.values(); + return mapIterator(it, (kv) => { + return kv.value; + }); + } + + [Symbol.iterator]() { + return this.entries(); + } +}