Skip to content

Commit

Permalink
chore(saslprep): provide a browser-compatible version of saslprep pac…
Browse files Browse the repository at this point in the history
…kage that doesn't use zlib (#202)
  • Loading branch information
gribnoysup authored Jan 15, 2024
1 parent eb7e757 commit a8e3821
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 48 deletions.
16 changes: 10 additions & 6 deletions packages/saslprep/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"publishConfig": {
"access": "public"
},
"main": "dist/index.js",
"main": "dist/node.js",
"bugs": {
"url": "https://jira.mongodb.org/projects/COMPASS/issues",
"email": "[email protected]"
Expand All @@ -28,18 +28,22 @@
],
"license": "MIT",
"exports": {
"browser": {
"types": "./dist/browser.d.ts",
"default": "./dist/browser.js"
},
"import": {
"types": "./dist/index.d.ts",
"types": "./dist/node.d.ts",
"default": "./dist/.esm-wrapper.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
"types": "./dist/node.d.ts",
"default": "./dist/node.js"
}
},
"types": "./dist/index.d.ts",
"types": "./dist/node.d.ts",
"scripts": {
"gen-code-points": "ts-node src/generate-code-points.ts src/code-points-data.ts",
"gen-code-points": "ts-node src/generate-code-points.ts src/code-points-data.ts src/code-points-data-browser.ts",
"bootstrap": "npm run compile",
"prepublishOnly": "npm run compile",
"compile": "npm run gen-code-points && tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs",
Expand Down
77 changes: 77 additions & 0 deletions packages/saslprep/src/browser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import saslprep from './browser';
import { expect } from 'chai';

const chr = String.fromCodePoint;

describe('saslprep (browser)', function () {
it('should work with latin letters', function () {
const str = 'user';
expect(saslprep(str)).to.equal(str);
});

it('should work be case preserved', function () {
const str = 'USER';
expect(saslprep(str)).to.equal(str);
});

it('should work with high code points (> U+FFFF)', function () {
const str = '\uD83D\uDE00';
expect(saslprep(str, { allowUnassigned: true })).to.equal(str);
});

it('should remove `mapped to nothing` characters', function () {
expect(saslprep('I\u00ADX')).to.equal('IX');
});

it('should replace `Non-ASCII space characters` with space', function () {
expect(saslprep('a\u00A0b')).to.equal('a\u0020b');
});

it('should normalize as NFKC', function () {
expect(saslprep('\u00AA')).to.equal('a');
expect(saslprep('\u2168')).to.equal('IX');
});

it('should throws when prohibited characters', function () {
// C.2.1 ASCII control characters
expect(() => saslprep('a\u007Fb')).to.throw();

// C.2.2 Non-ASCII control characters
expect(() => saslprep('a\u06DDb')).to.throw();

// C.3 Private use
expect(() => saslprep('a\uE000b')).to.throw();

// C.4 Non-character code points
expect(() => saslprep(`a${chr(0x1fffe)}b`)).to.throw();

// C.5 Surrogate codes
expect(() => saslprep('a\uD800b')).to.throw();

// C.6 Inappropriate for plain text
expect(() => saslprep('a\uFFF9b')).to.throw();

// C.7 Inappropriate for canonical representation
expect(() => saslprep('a\u2FF0b')).to.throw();

// C.8 Change display properties or are deprecated
expect(() => saslprep('a\u200Eb')).to.throw();

// C.9 Tagging characters
expect(() => saslprep(`a${chr(0xe0001)}b`)).to.throw();
});

it('should not containt RandALCat and LCat bidi', function () {
expect(() => saslprep('a\u06DD\u00AAb')).to.throw();
});

it('RandALCat should be first and last', function () {
expect(() => saslprep('\u0627\u0031\u0628')).not.to.throw();
expect(() => saslprep('\u0627\u0031')).to.throw();
});

it('should handle unassigned code points', function () {
expect(() => saslprep('a\u0487')).to.throw();
expect(() => saslprep('a\u0487', { allowUnassigned: true })).not.to.throw();
});
});
11 changes: 11 additions & 0 deletions packages/saslprep/src/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import _saslprep from './index';
import { createMemoryCodePoints } from './memory-code-points';
import data from './code-points-data-browser';

const codePoints = createMemoryCodePoints(data);

const saslprep = _saslprep.bind(null, codePoints);

Object.assign(saslprep, { saslprep, default: saslprep });

export = saslprep;
5 changes: 5 additions & 0 deletions packages/saslprep/src/code-points-data-browser.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions packages/saslprep/src/generate-code-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ export default gunzipSync(
);
`
);

const fsStreamUncompressedData = createWriteStream(process.argv[3]);

fsStreamUncompressedData.write(
`const data = Buffer.from('${Buffer.concat(memory).toString(
'base64'
)}', 'base64');\nexport default data;\n`
);
45 changes: 23 additions & 22 deletions packages/saslprep/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,4 @@
import {
unassigned_code_points,
commonly_mapped_to_nothing,
non_ASCII_space_characters,
prohibited_characters,
bidirectional_r_al,
bidirectional_l,
} from './memory-code-points';

// 2.1. Mapping

/**
* non-ASCII space characters [StringPrep, C.1.2] that can be
* mapped to SPACE (U+0020)
*/
const mapping2space = non_ASCII_space_characters;

/**
* the "commonly mapped to nothing" characters [StringPrep, B.1]
* that can be mapped to nothing.
*/
const mapping2nothing = commonly_mapped_to_nothing;
import type { createMemoryCodePoints } from './memory-code-points';

// utils
const getCodePoint = (character: string) => character.codePointAt(0);
Expand Down Expand Up @@ -58,9 +37,31 @@ function toCodePoints(input: string): number[] {
* SASLprep.
*/
function saslprep(
{
unassigned_code_points,
commonly_mapped_to_nothing,
non_ASCII_space_characters,
prohibited_characters,
bidirectional_r_al,
bidirectional_l,
}: ReturnType<typeof createMemoryCodePoints>,
input: string,
opts: { allowUnassigned?: boolean } = {}
): string {
// 2.1. Mapping

/**
* non-ASCII space characters [StringPrep, C.1.2] that can be
* mapped to SPACE (U+0020)
*/
const mapping2space = non_ASCII_space_characters;

/**
* the "commonly mapped to nothing" characters [StringPrep, B.1]
* that can be mapped to nothing.
*/
const mapping2nothing = commonly_mapped_to_nothing;

if (typeof input !== 'string') {
throw new TypeError('Expected string.');
}
Expand Down
46 changes: 28 additions & 18 deletions packages/saslprep/src/memory-code-points.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import bitfield from 'sparse-bitfield';
import memory from './code-points-data';

let offset = 0;
export function createMemoryCodePoints(data: Buffer) {
let offset = 0;

/**
* Loads each code points sequence from buffer.
*/
function read(): bitfield.BitFieldInstance {
const size = memory.readUInt32BE(offset);
offset += 4;
/**
* Loads each code points sequence from buffer.
*/
function read(): bitfield.BitFieldInstance {
const size = data.readUInt32BE(offset);
offset += 4;

const codepoints = memory.slice(offset, offset + size);
offset += size;
const codepoints = data.slice(offset, offset + size);
offset += size;

return bitfield({ buffer: codepoints });
}
return bitfield({ buffer: codepoints });
}

const unassigned_code_points = read();
const commonly_mapped_to_nothing = read();
const non_ASCII_space_characters = read();
const prohibited_characters = read();
const bidirectional_r_al = read();
const bidirectional_l = read();

export const unassigned_code_points = read();
export const commonly_mapped_to_nothing = read();
export const non_ASCII_space_characters = read();
export const prohibited_characters = read();
export const bidirectional_r_al = read();
export const bidirectional_l = read();
return {
unassigned_code_points,
commonly_mapped_to_nothing,
non_ASCII_space_characters,
prohibited_characters,
bidirectional_r_al,
bidirectional_l,
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import saslprep from './index';
import saslprep from './node';
import { expect } from 'chai';

const chr = String.fromCodePoint;

describe('saslprep', function () {
describe('saslprep (node)', function () {
it('should work with latin letters', function () {
const str = 'user';
expect(saslprep(str)).to.equal(str);
Expand Down
11 changes: 11 additions & 0 deletions packages/saslprep/src/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import _saslprep from './index';
import { createMemoryCodePoints } from './memory-code-points';
import data from './code-points-data';

const codePoints = createMemoryCodePoints(data);

const saslprep = _saslprep.bind(null, codePoints);

Object.assign(saslprep, { saslprep, default: saslprep });

export = saslprep;

0 comments on commit a8e3821

Please sign in to comment.