diff --git a/lib/base64.js b/lib/base64.js new file mode 100644 index 0000000..57d878a --- /dev/null +++ b/lib/base64.js @@ -0,0 +1,42 @@ +const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* The JS world is still in the stone age. We're in 2022 and we still don't + * have the technology to correctly base64-encode a UTF-8 string. Can't wait + * the next industrial revolution. + * + * For more info, see: + * https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem + */ +export function encode(data) { + if (!window.TextEncoder) { + return btoa(data); + } + + var encoder = new TextEncoder(); + var bytes = encoder.encode(data); + + var trailing = bytes.length % 3; + var out = ""; + for (var i = 0; i < bytes.length - trailing; i += 3) { + var u24 = (bytes[i] << 16) + (bytes[i + 1] << 8) + bytes[i + 2]; + out += alphabet[(u24 >> 18) & 0x3F]; + out += alphabet[(u24 >> 12) & 0x3F]; + out += alphabet[(u24 >> 6) & 0x3F]; + out += alphabet[u24 & 0x3F]; + } + + if (trailing == 1) { + var u8 = bytes[bytes.length - 1]; + out += alphabet[u8 >> 2]; + out += alphabet[(u8 << 4) & 0x3F]; + out += "=="; + } else if (trailing == 2) { + var u16 = (bytes[bytes.length - 2] << 8) + bytes[bytes.length - 1]; + out += alphabet[u16 >> 10]; + out += alphabet[(u16 >> 4) & 0x3F]; + out += alphabet[(u16 << 2) & 0x3F]; + out += "="; + } + + return out; +} diff --git a/lib/client.js b/lib/client.js index caaf042..70d59c0 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,4 +1,5 @@ import * as irc from "./irc.js"; +import * as base64 from "./base64.js"; // Static list of capabilities that are always requested when supported by the // server @@ -460,11 +461,11 @@ export default class Client extends EventTarget { let initialResp = null; switch (mechanism) { case "PLAIN": - let respStr = btoa("\0" + params.username + "\0" + params.password); + let respStr = base64.encode("\0" + params.username + "\0" + params.password); initialResp = { command: "AUTHENTICATE", params: [respStr] }; break; case "EXTERNAL": - initialResp = { command: "AUTHENTICATE", params: [btoa("")] }; + initialResp = { command: "AUTHENTICATE", params: [base64.encode("")] }; break; default: throw new Error(`Unknown authentication mechanism '${mechanism}'`);