tldts-utils 6.1.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +3 -0
  3. package/bin/update.js +7 -0
  4. package/dist/cjs/index.js +698 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/src/builders/hashes.js +186 -0
  7. package/dist/cjs/src/builders/hashes.js.map +1 -0
  8. package/dist/cjs/src/builders/trie.js +155 -0
  9. package/dist/cjs/src/builders/trie.js.map +1 -0
  10. package/dist/cjs/src/find-base-dir.js +13 -0
  11. package/dist/cjs/src/find-base-dir.js.map +1 -0
  12. package/dist/cjs/src/list.js +11 -0
  13. package/dist/cjs/src/list.js.map +1 -0
  14. package/dist/cjs/src/parser.js +63 -0
  15. package/dist/cjs/src/parser.js.map +1 -0
  16. package/dist/cjs/src/update.js +23 -0
  17. package/dist/cjs/src/update.js.map +1 -0
  18. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  19. package/dist/es6/index.js +4 -0
  20. package/dist/es6/index.js.map +1 -0
  21. package/dist/es6/src/builders/hashes.js +184 -0
  22. package/dist/es6/src/builders/hashes.js.map +1 -0
  23. package/dist/es6/src/builders/trie.js +153 -0
  24. package/dist/es6/src/builders/trie.js.map +1 -0
  25. package/dist/es6/src/find-base-dir.js +10 -0
  26. package/dist/es6/src/find-base-dir.js.map +1 -0
  27. package/dist/es6/src/list.js +8 -0
  28. package/dist/es6/src/list.js.map +1 -0
  29. package/dist/es6/src/parser.js +61 -0
  30. package/dist/es6/src/parser.js.map +1 -0
  31. package/dist/es6/src/update.js +20 -0
  32. package/dist/es6/src/update.js.map +1 -0
  33. package/dist/es6/tsconfig.bundle.tsbuildinfo +1 -0
  34. package/dist/types/index.d.ts +4 -0
  35. package/dist/types/src/builders/hashes.d.ts +90 -0
  36. package/dist/types/src/builders/trie.d.ts +28 -0
  37. package/dist/types/src/find-base-dir.d.ts +1 -0
  38. package/dist/types/src/list.d.ts +1 -0
  39. package/dist/types/src/parser.d.ts +12 -0
  40. package/dist/types/src/update.d.ts +1 -0
  41. package/index.ts +4 -0
  42. package/package.json +56 -0
  43. package/src/builders/hashes.ts +201 -0
  44. package/src/builders/trie.ts +216 -0
  45. package/src/find-base-dir.ts +11 -0
  46. package/src/list.ts +8 -0
  47. package/src/parser.ts +75 -0
  48. package/src/update.ts +36 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * This builder implements a fast and compact DAWG data-structure (a.k.a.:
3
+ * direct acyclic word graph, with suffix and prefix compression of the
4
+ * top-level domains) using JavaScript Objects. It is a good trade-off between
5
+ * correctness, speed and space. This is much faster then using a linear scan
6
+ * over all the rules, but slightly slower than the probabilistic
7
+ * data-structure implemented in `bin/builders/hashes.js`.
8
+ *
9
+ * Here is a simple example:
10
+ *
11
+ * co.uk
12
+ * gov.uk
13
+ * fr
14
+ *
15
+ * <root>
16
+ * |_ uk
17
+ * |_ co *
18
+ * |_ gov *
19
+ * |_ fr *
20
+ *
21
+ * Finding a match is then a matter of iterating on labels of a hostname
22
+ * backward and following the branches of the Trie. Whenever we find a terminal
23
+ * node (indicated with a '*' above), we consider this a match.
24
+ */
25
+
26
+ import parse from '../parser';
27
+
28
+ interface ITrie {
29
+ $: 0 | 1 | 2;
30
+ succ: { [label: string]: ITrie };
31
+ }
32
+
33
+ type CompactTrie = [0 | 1 | 2, { [label: string]: CompactTrie }];
34
+
35
+ function convertToCompactTrie(trie: ITrie): CompactTrie {
36
+ return [
37
+ trie.$,
38
+ Object.fromEntries(
39
+ Object.entries(trie.succ).map(([label, succ]) => [
40
+ label,
41
+ typeof succ === 'string' ? succ : convertToCompactTrie(succ),
42
+ ]),
43
+ ),
44
+ ];
45
+ }
46
+
47
+ /**
48
+ * Insert a public suffix rule in the `trie`.
49
+ */
50
+ function insertInTrie(
51
+ {
52
+ parts,
53
+ isIcann,
54
+ }: {
55
+ parts: string[];
56
+ isIcann: boolean;
57
+ },
58
+ trie: ITrie,
59
+ ): ITrie {
60
+ let node: ITrie = trie;
61
+
62
+ for (const part of parts) {
63
+ let nextNode = node.succ[part];
64
+ if (nextNode === undefined) {
65
+ nextNode = { $: 0, succ: {} };
66
+ node.succ[part] = nextNode;
67
+ }
68
+
69
+ node = nextNode;
70
+ }
71
+
72
+ node.$ = isIcann ? 1 : 2;
73
+
74
+ return trie;
75
+ }
76
+
77
+ let nextId = 0;
78
+
79
+ /**
80
+ * Compress the given `trie` into a DAWG (by compressing common suffixes as
81
+ * well as prefixes). This form is very efficient but cannot be dumped as a
82
+ * simple JSON into the file. The solution is to create intermediary variables
83
+ * for each shared sub-tree, which are then used in the main DAWG.
84
+ */
85
+ function compressToDAWG(trie: ITrie, name: string): string {
86
+ const nodesByHash = new Map<string, ITrie[]>();
87
+ const replaceNodes = new Map<ITrie, (newNode: ITrie) => void>();
88
+
89
+ (function groupNodesByHash(node: ITrie): string {
90
+ // Get a sorted list of next labels from this node
91
+ const nexts = Object.entries(node.succ)
92
+ .filter(([n]) => n !== '$')
93
+ .sort();
94
+
95
+ // Create a setter which can be used later to replace the value of a node.
96
+ // Each node is associated with a lambda function accepting one argument:
97
+ // the new value of the node. Because the context of the node (parent and
98
+ // label) is captured in the closure, it allows to just call it later, in a
99
+ // different context, to change the value of a given node in-place.
100
+ nexts.forEach(([n, succ]) => {
101
+ replaceNodes.set(succ, (newNode: ITrie) => {
102
+ node.succ[n] = newNode;
103
+ });
104
+ });
105
+
106
+ // Compute a unique hash for this node recursively.
107
+ const hash = `(${node.$},${[
108
+ ...nexts.map(([c, succ]) => `${c},${groupNodesByHash(succ)}`),
109
+ ].join('|')})`;
110
+
111
+ // Keep track of all node's hashes
112
+ let nodes: undefined | ITrie[] = nodesByHash.get(hash);
113
+ if (nodes === undefined) {
114
+ nodes = [];
115
+ nodesByHash.set(hash, nodes);
116
+ }
117
+ nodes.push(node);
118
+
119
+ return hash;
120
+ })(trie);
121
+
122
+ // Given `nodesByHash`, which associates a list of nodes to each hash
123
+ // encountered in the previous step, we will detect all the sub-trees being
124
+ // seen more than once and store them in a variable to allow sharing with
125
+ // different parts in the DAWG.
126
+ const variables: string[] = [];
127
+ nodesByHash.forEach((nodes) => {
128
+ if (nodes.length > 1) {
129
+ // Dump one of the nodes (they are all the same so it does not matter which one)
130
+ variables.push(
131
+ `_${nextId}: ITrie = ${JSON.stringify(
132
+ convertToCompactTrie(nodes[0]!),
133
+ )}`,
134
+ );
135
+
136
+ // Replace all the other ones by the name of this new variable
137
+ nodes.forEach((node) => {
138
+ // @ts-expect-error
139
+ replaceNodes.get(node)?.(`_${nextId}`);
140
+ });
141
+
142
+ nextId += 1;
143
+ }
144
+ });
145
+
146
+ const output = [];
147
+
148
+ // Create TypeScript source code to declare all of these variables. Because
149
+ // the value of nodes has been set to a value of the form '"_id"', we need to
150
+ // replace this on the fly to '_id'. We use a RegExp for this.
151
+ if (variables.length !== 0) {
152
+ let variablesSourceCode = `const ${variables.join(',')};`;
153
+ for (let i = 0; i < nextId; i += 1) {
154
+ variablesSourceCode = variablesSourceCode.replace(
155
+ new RegExp(`"_${i}"`, 'g'),
156
+ `_${i}`,
157
+ );
158
+ }
159
+ output.push(variablesSourceCode);
160
+ }
161
+
162
+ // Dump the root of the DAWG as well, and perform the same replacement of _id
163
+ // parts than for the variables above.
164
+ let serializedTrie = JSON.stringify(convertToCompactTrie(trie));
165
+ for (let i = 0; i < nextId; i += 1) {
166
+ serializedTrie = serializedTrie.replace(
167
+ new RegExp(`"_${i}"`, 'g'),
168
+ `_${i}`,
169
+ );
170
+ }
171
+ output.push(`const ${name}: ITrie = ${serializedTrie};`);
172
+
173
+ return output.join('\n');
174
+ }
175
+
176
+ function convertToTypeScriptSource(
177
+ rules: ITrie,
178
+ exceptions: ITrie,
179
+ { includePrivate }: { includePrivate: boolean },
180
+ ): string {
181
+ return `
182
+ export type ITrie = [${
183
+ includePrivate ? '0 | 1 | 2' : '0 | 1'
184
+ }, { [label: string]: ITrie}];
185
+
186
+ export const exceptions: ITrie = (function() {
187
+ ${compressToDAWG(exceptions, 'exceptions')}
188
+ return exceptions;
189
+ })();
190
+
191
+ export const rules: ITrie = (function() {
192
+ ${compressToDAWG(rules, 'rules')}
193
+ return rules;
194
+ })();
195
+ `;
196
+ }
197
+
198
+ export default (
199
+ body: string,
200
+ { includePrivate }: { includePrivate: boolean },
201
+ ) => {
202
+ const exceptions: ITrie = { $: 0, succ: {} };
203
+ const rules: ITrie = { $: 0, succ: {} };
204
+
205
+ // Iterate on public suffix rules
206
+ parse(body, ({ rule, isIcann, isException }) => {
207
+ if (isIcann || includePrivate) {
208
+ insertInTrie(
209
+ { isIcann, parts: rule.split('.').reverse() },
210
+ isException ? exceptions : rules,
211
+ );
212
+ }
213
+ });
214
+
215
+ return convertToTypeScriptSource(rules, exceptions, { includePrivate });
216
+ };
@@ -0,0 +1,11 @@
1
+ import { existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+
4
+ export default function (target: string): string {
5
+ let baseDir = __dirname;
6
+ while (!existsSync(resolve(baseDir, target))) {
7
+ baseDir = resolve(baseDir, '../');
8
+ }
9
+
10
+ return resolve(baseDir, target);
11
+ }
package/src/list.ts ADDED
@@ -0,0 +1,8 @@
1
+ import findBaseDir from './find-base-dir';
2
+ import { readFileSync } from 'fs';
3
+
4
+ export default function (): string {
5
+ return readFileSync(findBaseDir('./publicsuffix/public_suffix_list.dat'), {
6
+ encoding: 'utf-8',
7
+ });
8
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { toASCII } from 'punycode/';
2
+
3
+ export interface IRule {
4
+ isException: boolean;
5
+ isIcann: boolean;
6
+ isNormal: boolean;
7
+ isWildcard: boolean;
8
+ rule: string;
9
+ }
10
+
11
+ /**
12
+ * Parse public suffix list and invoke callback on each rule.
13
+ */
14
+ export default (body: string, cb: (_: IRule) => void) => {
15
+ const beginPrivateDomains = '// ===begin private domains===';
16
+ let isIcann = true;
17
+
18
+ // Iterate on lines and extract public suffix rules
19
+ const lines: string[] = body.split('\n');
20
+ for (let line of lines) {
21
+ line = line.trim().toLowerCase();
22
+
23
+ // Ignore comments
24
+ if (line.length === 0 || line.startsWith('//')) {
25
+ if (line.startsWith(beginPrivateDomains)) {
26
+ isIcann = false;
27
+ }
28
+
29
+ continue;
30
+ }
31
+
32
+ let isException = false;
33
+ let isWildcard = false;
34
+ let isNormal = false;
35
+
36
+ // Select correct section to insert the rule
37
+ if (line.startsWith('!')) {
38
+ isException = true;
39
+ line = line.slice(1);
40
+ } else if (line.startsWith('*.')) {
41
+ isWildcard = true;
42
+ } else {
43
+ isNormal = true;
44
+ }
45
+
46
+ // Specification says that we should only consider the content of a line up
47
+ // to the first whitespace encountered.
48
+ const spaceIndex = line.indexOf(' ');
49
+ if (spaceIndex !== -1) {
50
+ line = line.substr(0, spaceIndex);
51
+ }
52
+
53
+ // If suffix is not ascii, we index the suffix twice so that we support IDNA
54
+ // labels as well. This allows to not have to perform the conversion at
55
+ // runtime.
56
+ const encoded = toASCII(line);
57
+ if (line !== encoded) {
58
+ cb({
59
+ isException,
60
+ isIcann,
61
+ isNormal,
62
+ isWildcard,
63
+ rule: encoded,
64
+ });
65
+ }
66
+
67
+ cb({
68
+ isException,
69
+ isIcann,
70
+ isNormal,
71
+ isWildcard,
72
+ rule: line,
73
+ });
74
+ }
75
+ };
package/src/update.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { writeFileSync } from 'fs';
2
+
3
+ import findBaseDir from './find-base-dir';
4
+ import loadPublicSuffixList from './list';
5
+ import buildHashes from './builders/hashes';
6
+ import buildTrie from './builders/trie';
7
+
8
+ export default function () {
9
+ console.log('Updating rules...');
10
+ const publicSuffixList = loadPublicSuffixList();
11
+
12
+ // Build trie and update TypeScript file
13
+ writeFileSync(
14
+ findBaseDir('./tldts/src/data/trie.ts'),
15
+ buildTrie(publicSuffixList, { includePrivate: true }),
16
+ 'utf-8',
17
+ );
18
+
19
+ // Build trie and update TypeScript file (ICANN only)
20
+ writeFileSync(
21
+ findBaseDir('./tldts-icann/src/data/trie.ts'),
22
+ buildTrie(publicSuffixList, { includePrivate: false }),
23
+ 'utf-8',
24
+ );
25
+
26
+ // Build hashes and update TypeScript file
27
+ const packed = buildHashes(publicSuffixList);
28
+ writeFileSync(
29
+ findBaseDir('./tldts-experimental/src/data/hashes.ts'),
30
+ `
31
+ // Code automatically generated using ./bin/builders/hashes.ts
32
+ export default new Uint32Array([${Array.from(packed).toString()}]);
33
+ `,
34
+ 'utf-8',
35
+ );
36
+ }