From a758b39ded59b0dc74b057ff4961f092ec46214a Mon Sep 17 00:00:00 2001 From: pluth Date: Sun, 1 Oct 2023 12:38:00 +0200 Subject: [PATCH] spec-suggested character mapping for virtual localparts --- spec/unit/IrcServer.spec.js | 52 +++++++++++++++++++++++++++++++++++++ src/irc/IrcServer.ts | 19 +++++++++++--- 2 files changed, 68 insertions(+), 3 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 c9cce65e8..34e0cb4ff 100644 --- a/src/irc/IrcServer.ts +++ b/src/irc/IrcServer.ts @@ -545,10 +545,16 @@ 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, - }).substring(1).toLowerCase(); // the first character is guaranteed by config schema to be '@' + nick: escaped2, + }).substring(1); // the first character is guaranteed by config schema to be '@' } public claimsUserId(userId: string): boolean { @@ -582,7 +588,14 @@ 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 {