From 15a6604df082cc46ba5ac0b5cc8350bc4faecfc6 Mon Sep 17 00:00:00 2001 From: Kai Bleeke Date: Sat, 21 Oct 2023 13:54:24 +0200 Subject: [PATCH 1/3] missing node-gyp dependency Signed-off-by: Kai Bleeke --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index d29892a03..721b52d39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,9 @@ RUN cd freebindfree && make # Typescript build FROM node:18 as builder +RUN apt-get update && apt-get install -y node-gyp --no-install-recommends +RUN rm -rf /var/lib/apt/lists/* + WORKDIR /build COPY src/ /build/src/ From 57e947242f746387c3b31886570b2d712a0198c1 Mon Sep 17 00:00:00 2001 From: Kai Bleeke Date: Sat, 21 Oct 2023 13:54:32 +0200 Subject: [PATCH 2/3] spec-suggested character mapping for virtual localparts Signed-off-by: Kai Bleeke --- spec/unit/IrcServer.spec.js | 52 +++++++++++++++++++++++++++++++++++++ src/irc/IrcServer.ts | 21 +++++++++++---- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/spec/unit/IrcServer.spec.js b/spec/unit/IrcServer.spec.js index 5beb65217..ec454cddb 100644 --- a/spec/unit/IrcServer.spec.js +++ b/spec/unit/IrcServer.spec.js @@ -69,5 +69,57 @@ describe("IrcServer", function() { ); expect(() => {server.getNick("@💩ケ:foobar")}).toThrow(); }); + }) + describe("getUserLocalpart", function() { + it("does not touch valid characters", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getUserLocalpart("foobar09.-+")).toEqual("irc.foobar_foobar09.-+"); + }); + it("encodes capital letters", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getUserLocalpart("foOBaR_09.-+")).toEqual("irc.foobar_fo_o_ba_r__09.-+"); + }); + it("encodes invalid characters", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getUserLocalpart("foobar=[m]")).toEqual("irc.foobar_foobar=3d=5bm=5d"); + }); + it("encodes both capital letters and invalid chars", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getUserLocalpart("f_oObAr=[m]")).toEqual("irc.foobar_f__o_ob_ar=3d=5bm=5d"); + }); + }); + describe("getNickFromUserId", function() { + it("does not touch valid characters", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNickFromUserId("irc.foobar_foobar09.-+")).toEqual("foobar09.-+"); + }); + it("encodes capital letters", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNickFromUserId("irc.foobar_fo_o_ba_r__09.-+")).toEqual("foOBaR_09.-+"); + }); + it("decodes invalid characters", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNickFromUserId("irc.foobar_foobar=3d=5bm=5d")).toEqual("foobar=[m]"); + }); + it("encodes both capital letters and invalid chars", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNickFromUserId("irc.foobar_f__o_ob_ar=3d=5bm=5d")).toEqual("f_oObAr=[m]"); + }); }); }); diff --git a/src/irc/IrcServer.ts b/src/irc/IrcServer.ts index c9e42af9e..9d1ef30f3 100644 --- a/src/irc/IrcServer.ts +++ b/src/irc/IrcServer.ts @@ -535,9 +535,15 @@ export class IrcServer { public getUserLocalpart(nick: string): string { // the template is just a literal string with special vars; so find/replace // the vars and strip the @ + + // https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets + const escaped = nick.replaceAll(/[A-Z_]/g, (c) => "_" + c.toLowerCase()); + const escaped2 = escaped.replaceAll(/[^a-z0-9\.\_\-\/+]/g, + (c) => "=" + c.charCodeAt(0).toString(16).padStart(2, '0')); + return renderTemplate(this.config.matrixClients.userTemplate, { server: this.domain, - nick, + nick: escaped2, }).substring(1); // the first character is guaranteed by config schema to be '@' } @@ -572,13 +578,18 @@ export class IrcServer { if (!match) { return null; } - return match[1]; + + // https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets + const unescaped = match[1].replaceAll(/=([0-9a-f][0-9a-f])/g, + (_m, g1) => String.fromCharCode(parseInt(g1, 16))); + + const unescaped2 = unescaped.replaceAll(/_([a-z_])/g, (m, g1) => g1.toUppercase()); + + return unescaped2; } public getUserIdFromNick(nick: string): string { - const template = this.config.matrixClients.userTemplate; - return template.replace(/\$NICK/g, nick).replace(/\$SERVER/g, this.domain) + - ":" + this.homeserverDomain; + return "@" + this.getUserLocalpart(nick) + ":" + this.homeserverDomain; } public getDisplayNameFromNick(nick: string): string { From f0dae6be941ffdc785c06d790204eb4336970771 Mon Sep 17 00:00:00 2001 From: Kai Bleeke Date: Sat, 21 Oct 2023 13:54:35 +0200 Subject: [PATCH 3/3] Config option for matrix id character mapping Signed-off-by: Kai Bleeke --- config.sample.yaml | 5 ++++ config.schema.yml | 4 ++++ spec/unit/IrcServer.spec.js | 48 ++++++++++++++++++++++++++++++------- src/irc/IrcServer.ts | 32 +++++++++++++++---------- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index 7b0cb1ed2..ec50309bc 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -315,6 +315,11 @@ ircService: # $NICK => The IRC nick # $SERVER => The IRC server address (e.g. "irc.example.com") matrixClients: + # Character mapping for matrix IDs localparts + # 0: No character mapping + # 1: Mapping according to + # https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets + localPartCharacterMapping: 0 # The user ID template to use when creating virtual matrix users. This # MUST start with an @ and have $NICK somewhere in it. # Optional. Default: "@$SERVER_$NICK". diff --git a/config.schema.yml b/config.schema.yml index 53017ee55..14f30fb50 100644 --- a/config.schema.yml +++ b/config.schema.yml @@ -413,6 +413,10 @@ properties: matrixClients: type: "object" properties: + localPartCharacterMapping: + type: "integer" + minimum: 0 + maximum: 1 userTemplate: type: "string" pattern: "^@.*\\$NICK" diff --git a/spec/unit/IrcServer.spec.js b/spec/unit/IrcServer.spec.js index ec454cddb..2a637fbcc 100644 --- a/spec/unit/IrcServer.spec.js +++ b/spec/unit/IrcServer.spec.js @@ -73,25 +73,41 @@ describe("IrcServer", function() { describe("getUserLocalpart", function() { it("does not touch valid characters", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getUserLocalpart("foobar09.-+")).toEqual("irc.foobar_foobar09.-+"); }); it("encodes capital letters", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getUserLocalpart("foOBaR_09.-+")).toEqual("irc.foobar_fo_o_ba_r__09.-+"); }); it("encodes invalid characters", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getUserLocalpart("foobar=[m]")).toEqual("irc.foobar_foobar=3d=5bm=5d"); }); it("encodes both capital letters and invalid chars", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getUserLocalpart("f_oObAr=[m]")).toEqual("irc.foobar_f__o_ob_ar=3d=5bm=5d"); }); @@ -99,25 +115,41 @@ describe("IrcServer", function() { describe("getNickFromUserId", function() { it("does not touch valid characters", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getNickFromUserId("irc.foobar_foobar09.-+")).toEqual("foobar09.-+"); }); it("encodes capital letters", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getNickFromUserId("irc.foobar_fo_o_ba_r__09.-+")).toEqual("foOBaR_09.-+"); }); it("decodes invalid characters", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getNickFromUserId("irc.foobar_foobar=3d=5bm=5d")).toEqual("foobar=[m]"); }); it("encodes both capital letters and invalid chars", function() { const server = new IrcServer("irc.foobar", - extend(true, IrcServer.DEFAULT_CONFIG, {}) + extend(true, IrcServer.DEFAULT_CONFIG, { + matrixClients: { + localPartCharacterMapping: 1, + } + }) ); expect(server.getNickFromUserId("irc.foobar_f__o_ob_ar=3d=5bm=5d")).toEqual("f_oObAr=[m]"); }); diff --git a/src/irc/IrcServer.ts b/src/irc/IrcServer.ts index 9d1ef30f3..81ad9fc26 100644 --- a/src/irc/IrcServer.ts +++ b/src/irc/IrcServer.ts @@ -78,6 +78,7 @@ export interface IrcServerConfig { federate: boolean; }; matrixClients: { + localPartCharacterMapping: number, userTemplate: string; displayName: string; joinAttempts: number; @@ -533,17 +534,19 @@ export class IrcServer { } public getUserLocalpart(nick: string): string { - // the template is just a literal string with special vars; so find/replace - // the vars and strip the @ - // https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets - const escaped = nick.replaceAll(/[A-Z_]/g, (c) => "_" + c.toLowerCase()); - const escaped2 = escaped.replaceAll(/[^a-z0-9\.\_\-\/+]/g, - (c) => "=" + c.charCodeAt(0).toString(16).padStart(2, '0')); + if (this.config.matrixClients.localPartCharacterMapping == 1) { + // https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets + nick = nick.replaceAll(/[A-Z_]/g, (c) => "_" + c.toLowerCase()); + nick = nick.replaceAll(/[^a-z0-9\.\_\-\/+]/g, + (c) => "=" + c.charCodeAt(0).toString(16).padStart(2, '0')); + } + // the template is just a literal string with special vars; so find/replace + // the vars and strip the @ return renderTemplate(this.config.matrixClients.userTemplate, { server: this.domain, - nick: escaped2, + nick, }).substring(1); // the first character is guaranteed by config schema to be '@' } @@ -579,13 +582,15 @@ export class IrcServer { return null; } - // https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets - const unescaped = match[1].replaceAll(/=([0-9a-f][0-9a-f])/g, - (_m, g1) => String.fromCharCode(parseInt(g1, 16))); - - const unescaped2 = unescaped.replaceAll(/_([a-z_])/g, (m, g1) => g1.toUppercase()); + let nick = match[1]; + if (this.config.matrixClients.localPartCharacterMapping == 1) { + // https://spec.matrix.org/v1.8/appendices/#mapping-from-other-character-sets + nick = match[1].replaceAll(/=([0-9a-f][0-9a-f])/g, + (_m, g1) => String.fromCharCode(parseInt(g1, 16))); + nick = nick.replaceAll(/_([a-z_])/g, (m, g1) => g1.toUppercase()); + } - return unescaped2; + return nick; } public getUserIdFromNick(nick: string): string { @@ -733,6 +738,7 @@ export class IrcServer { mappings: {}, excludedUsers: [], matrixClients: { + localPartCharacterMapping: 0, userTemplate: "@$SERVER_$NICK", displayName: "$NICK", joinAttempts: -1,