JavaScript generic ASN.1 parser (mirror)

Compare changes

Choose any two refs to compare.

+2294 -269
+1 -2
.github/workflows/node.js.yml
··· 20 20 uses: actions/setup-node@v4 21 21 with: 22 22 node-version: ${{ matrix.node-version }} 23 - - run: npm install 24 23 - run: npm test all 25 - - run: npm run testdefs 24 + - run: npm install 26 25 if: matrix.node-version == 'latest' 27 26 - run: npm run lint 28 27 if: matrix.node-version == 'latest'
+2
.gitignore
··· 2 2 .DS_Store/ 3 3 .vscode/ 4 4 node_modules/ 5 + dist/ 5 6 package-lock.json 6 7 pnpm-lock.yaml 7 8 # Artifacts from release.sh 9 + index-local.html 8 10 sha256sums.asc 9 11 asn1js.zip 10 12 # Artifacts from mirror_to_github.sh
+2
.mtn-ignore
··· 1 1 [.]DS_Store$ 2 2 [.]vscode$ 3 3 node_modules$ 4 + dist$ 4 5 package-lock[.]json 5 6 pnpm-lock[.]yaml 6 7 # Artifacts from release.sh 8 + index-local.html 7 9 sha256sums[.]asc 8 10 asn1js[.]zip 9 11 # Artifacts from mirror_to_github.sh
+75
.vscode/launch.json
··· 1 + { 2 + // Use IntelliSense to learn about possible attributes. 3 + // Hover to view descriptions of existing attributes. 4 + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 + "version": "0.2.0", 6 + "configurations": [ 7 + { 8 + "type": "node", 9 + "request": "launch", 10 + "name": "dumpASN1", 11 + "skipFiles": [ 12 + "<node_internals>/**" 13 + ], 14 + "program": "${workspaceFolder}/dumpASN1.js", 15 + "args": [ 16 + "examples/ed25519.cer" 17 + ] 18 + }, 19 + { 20 + "type": "node", 21 + "request": "launch", 22 + "name": "parseRFC", 23 + "skipFiles": [ 24 + "<node_internals>/**" 25 + ], 26 + "program": "${workspaceFolder}/parseRFC.js", 27 + "args": [ 28 + "rfc/rfc4511.txt", 29 + "rfcdef.json" 30 + ] 31 + }, 32 + { 33 + "type": "node", 34 + "request": "launch", 35 + "name": "dumpASN1 cert", 36 + "skipFiles": [ 37 + "<node_internals>/**" 38 + ], 39 + "program": "${workspaceFolder}/dumpASN1.js", 40 + "args": [ 41 + "data:base64,MIG9AgEBMA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUUjM0IFNhbXBsZXMxGzAZBgNVBAMTElRSMzQgU2FtcGxlIENBIEtESBcNMTAxMTAyMTczMzMwWhcNMTAxMjAyMTczMzMwWjBIMBYCBTQAAAAIFw0xMDExMDIxNzI4MTNaMBYCBTQAAAAKFw0xMDExMDIxNzMxNDZaMBYCBTQAAAALFw0xMDExMDIxNzMzMjVa", 42 + "1.3.6.1.5.5.7.0.18", 43 + "TBSCertList" 44 + ] 45 + }, 46 + { 47 + "type": "node", 48 + "request": "launch", 49 + "name": "dumpASN1 CMS", 50 + "skipFiles": [ 51 + "<node_internals>/**" 52 + ], 53 + "program": "${workspaceFolder}/dumpASN1.js", 54 + "args": [ 55 + "examples/cms-password.p7m", 56 + "1.2.840.113549.1.9.16.0.14", 57 + "ContentInfo" 58 + ] 59 + }, 60 + { 61 + "type": "node", 62 + "request": "launch", 63 + "name": "dumpASN1 LDAP", 64 + "skipFiles": [ 65 + "<node_internals>/**" 66 + ], 67 + "program": "${workspaceFolder}/dumpASN1.js", 68 + "args": [ 69 + "data:base64,MDMCAQFjLgQACgEACgEAAgEAAgEAAQEAoA+jDQQFTnRWZXIEBAEAAAAwCgQITmV0bG9nb24===", 70 + "1.3.6.1.1.18", 71 + "LDAPMessage" 72 + ] 73 + } 74 + ] 75 + }
+9
.vscode/settings.json
··· 1 + { 2 + "editor.insertSpaces": true, 3 + "editor.tabSize": 8, 4 + "editor.indentSize": 2, 5 + "editor.stickyScroll.enabled": true, 6 + "explorer.excludeGitIgnore": true, 7 + "files.eol": "\n", 8 + "git.openRepositoryInParentFolders": "never" 9 + }
+1 -1
LICENSE
··· 1 1 ISC License 2 2 3 - Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 3 + Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it> 4 4 5 5 Permission to use, copy, modify, and/or distribute this software for any 6 6 purpose with or without fee is hereby granted, provided that the above
+42 -9
README.md
··· 3 3 4 4 asn1js is a JavaScript generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures. 5 5 6 - An example page that can decode Base64-encoded (raw base64, PEM armoring and `begin-base64` are recognized) or Hex-encoded (or local files with some browsers) is included and can be used both [online on the official website](https://lapo.it/asn1js/) or [offline (ZIP file)](https://lapo.it/asn1js/asn1js.zip). 6 + An example page that can decode Base64-encoded (raw base64, PEM armoring and `begin-base64` are recognized) or Hex-encoded (or local files with some browsers) is included and can be used both [online on the official website](https://asn1js.eu/) or [offline (ZIP file)](https://lapo.it/asn1js/asn1js.zip) by opening `index-local.html`. 7 7 8 - Usage with `npm` / `yarn` 9 - ------------------------- 8 + Usage with `nodejs` 9 + ------------------- 10 10 11 11 This package can be installed with either npm or yarn via the following commands: 12 12 ··· 17 17 yarn add @lapo/asn1js 18 18 ``` 19 19 20 - Assuming a standard javascript bundler is setup you can import it like so: 20 + You can import the classes like this: 21 21 22 22 ```js 23 - import ASN1 from '@lapo/asn1js'; 23 + import { ASN1 } from '@lapo/asn1js'; 24 24 ``` 25 25 26 26 A submodule of this package can also be imported: 27 27 28 28 ```js 29 - import Hex from '@lapo/asn1js/hex'; 29 + import { Hex } from '@lapo/asn1js/hex.js'; 30 30 ``` 31 31 32 - Unfortunately until [`require(esm)` gets released](https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/) it is necessary to use async `import()` when used from CommonJS (legacy NodeJS) code. 32 + If your code is still not using ES6 Modules (and is using CommonJS) you can `require` it normally [since NodeJS 22](https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/) (with parameter `--experimental-require-module`): 33 + 34 + ```js 35 + const 36 + { ASN1 } = require('@lapo/asn1js'), 37 + { Hex } = require('@lapo/asn1js/hex.js'); 38 + console.log(ASN1.decode(Hex.decode('06032B6570')).content()); 39 + ``` 40 + 41 + On older NodeJS you instead need to use async `import`: 42 + 43 + ```js 44 + async function main() { 45 + const 46 + { ASN1 } = await import('@lapo/asn1js'), 47 + { Hex } = await import('@lapo/asn1js/hex.js'); 48 + console.log(ASN1.decode(Hex.decode('06032B6570')).content()); 49 + } 50 + main(); 51 + ``` 33 52 34 53 Usage on the web 35 54 -------------------- ··· 38 57 39 58 ```html 40 59 <script> 41 - import { ASN1} from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js'; 60 + import { ASN1 } from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js'; 42 61 import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js'; 43 62 44 63 document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content(); 45 64 </script> 46 65 ``` 47 66 67 + Local usage 68 + -------------------- 69 + 70 + Since unfortunately ESM modules are not working on `file:` protocol due to [CORS issues](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts), there is a bundled [single-file version working locally](https://asn1js.eu/index-local.html). It doesn't work online (due to [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) restrictions about inline content) but can be saved locally and opened in a browser. 71 + 72 + Usage from CLI 73 + -------------------- 74 + 75 + You can dump an ASN.1 structure from the command line using the following command (no need to even install it): 76 + 77 + ```sh 78 + npx @lapo/asn1js ed25519.cer 79 + ``` 80 + 48 81 ISC license 49 82 ----------- 50 83 51 - ASN.1 JavaScript decoder Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 84 + ASN.1 JavaScript decoder Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it> 52 85 53 86 Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 54 87
+50 -22
asn1.js
··· 1 1 // ASN.1 JavaScript decoder 2 - // Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 2 + // Copyright (c) 2008 Lapo Luchini <lapo@lapo.it> 3 3 4 4 // Permission to use, copy, modify, and/or distribute this software for any 5 5 // purpose with or without fee is hereby granted, provided that the above 6 6 // copyright notice and this permission notice appear in all copies. 7 - // 7 + // 8 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ··· 21 21 reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/, 22 22 reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/, 23 23 hexDigits = '0123456789ABCDEF', 24 - b64Safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 24 + b64Std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 25 + b64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 25 26 tableT61 = [ 26 27 ['', ''], 27 28 ['AEIOUaeiou', 'ร€รˆรŒร’ร™ร รจรฌรฒรน'], // Grave ··· 56 57 } 57 58 } 58 59 59 - class Stream { 60 + /** Class to manage a stream of bytes, with a zero-copy approach. 61 + * It uses an existing array or binary string and advances a position index. */ 62 + export class Stream { 60 63 64 + /** 65 + * @param {Stream|array|string} enc data (will not be copied) 66 + * @param {?number} pos starting position (mandatory when `end` is not a Stream) 67 + */ 61 68 constructor(enc, pos) { 62 69 if (enc instanceof Stream) { 63 70 this.enc = enc.enc; 64 71 this.pos = enc.pos; 65 72 } else { 66 - // enc should be an array or a binary string 67 73 this.enc = enc; 68 74 this.pos = pos; 69 75 } 76 + if (typeof this.pos != 'number') 77 + throw new Error('"pos" must be a numeric value'); 78 + if (typeof this.enc == 'string') 79 + this.getRaw = pos => this.enc.charCodeAt(pos); 80 + else if (typeof this.enc[0] == 'number') 81 + this.getRaw = pos => this.enc[pos]; 82 + else 83 + throw new Error('"enc" must be a numeric array or a string'); 70 84 } 85 + /** Get the byte at current position (and increment it) or at a specified position (and avoid moving current position). 86 + * @param {?number} pos read position if specified, else current position (and increment it) */ 71 87 get(pos) { 72 88 if (pos === undefined) 73 89 pos = this.pos++; 74 90 if (pos >= this.enc.length) 75 91 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length); 76 - return (typeof this.enc == 'string') ? this.enc.charCodeAt(pos) : this.enc[pos]; 92 + return this.getRaw(pos); 77 93 } 78 - hexByte(b) { 94 + /** Convert a single byte to an hexadcimal string (of length 2). 95 + * @param {number} b */ 96 + static hexByte(b) { 79 97 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF); 80 98 } 81 - /** Hexadecimal dump. 82 - * @param type 'raw', 'byte' or 'dump' */ 99 + /** Hexadecimal dump of a specified region of the stream. 100 + * @param {number} start starting position (included) 101 + * @param {number} end ending position (excluded) 102 + * @param {string} type 'raw', 'byte' or 'dump' (default) */ 83 103 hexDump(start, end, type = 'dump') { 84 104 let s = ''; 85 105 for (let i = start; i < end; ++i) { 86 106 if (type == 'byte' && i > start) 87 107 s += ' '; 88 - s += this.hexByte(this.get(i)); 108 + s += Stream.hexByte(this.get(i)); 89 109 if (type == 'dump') 90 110 switch (i & 0xF) { 91 111 case 0x7: s += ' '; break; ··· 95 115 } 96 116 return s; 97 117 } 98 - b64Dump(start, end) { 118 + /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5). 119 + * @param {number} start starting position (included) 120 + * @param {number} end ending position (excluded) 121 + * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */ 122 + b64Dump(start, end, type = 'url') { 123 + const b64 = type === 'url' ? b64URL : b64Std; 99 124 let extra = (end - start) % 3, 100 125 s = '', 101 126 i, c; 102 127 for (i = start; i + 2 < end; i += 3) { 103 128 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); 104 - s += b64Safe.charAt(c >> 18 & 0x3F); 105 - s += b64Safe.charAt(c >> 12 & 0x3F); 106 - s += b64Safe.charAt(c >> 6 & 0x3F); 107 - s += b64Safe.charAt(c & 0x3F); 129 + s += b64.charAt(c >> 18 & 0x3F); 130 + s += b64.charAt(c >> 12 & 0x3F); 131 + s += b64.charAt(c >> 6 & 0x3F); 132 + s += b64.charAt(c & 0x3F); 108 133 } 109 134 if (extra > 0) { 110 135 c = this.get(i) << 16; 111 136 if (extra > 1) c |= this.get(i + 1) << 8; 112 - s += b64Safe.charAt(c >> 18 & 0x3F); 113 - s += b64Safe.charAt(c >> 12 & 0x3F); 114 - if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F); 137 + s += b64.charAt(c >> 18 & 0x3F); 138 + s += b64.charAt(c >> 12 & 0x3F); 139 + if (extra == 2) s += b64.charAt(c >> 6 & 0x3F); 140 + if (b64 === b64Std) s += '==='.slice(0, 3 - extra); 115 141 } 116 142 return s; 117 143 } ··· 277 303 end = start + maxLength; 278 304 s = ''; 279 305 for (let i = start; i < end; ++i) 280 - s += this.hexByte(this.get(i)); 306 + s += Stream.hexByte(this.get(i)); 281 307 if (len > maxLength) 282 308 s += ellipsis; 283 309 return { size: len, str: s }; ··· 535 561 toHexString(type = 'raw') { 536 562 return this.stream.hexDump(this.posStart(), this.posEnd(), type); 537 563 } 538 - /** Base64 dump of the node. */ 539 - toB64String() { 540 - return this.stream.b64Dump(this.posStart(), this.posEnd()); 564 + /** Base64url dump of the node (according to RFC 4648 section 5). 565 + * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) 566 + */ 567 + toB64String(type = 'url') { 568 + return this.stream.b64Dump(this.posStart(), this.posEnd(), type); 541 569 } 542 570 static decodeLength(stream) { 543 571 let buf = stream.get(),
+8 -7
base64.js
··· 1 1 // Base64 JavaScript decoder 2 - // Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 2 + // Copyright (c) 2008 Lapo Luchini <lapo@lapo.it> 3 3 4 4 // Permission to use, copy, modify, and/or distribute this software for any 5 5 // purpose with or without fee is hereby granted, provided that the above 6 6 // copyright notice and this permission notice appear in all copies. 7 - // 7 + // 8 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ··· 31 31 decoder[b64.charCodeAt(i)] = i; 32 32 for (i = 0; i < ignore.length; ++i) 33 33 decoder[ignore.charCodeAt(i)] = -1; 34 - // RFC 3548 URL & file safe encoding 34 + // also support decoding Base64url (RFC 4648 section 5) 35 35 decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; 36 36 decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; 37 37 } ··· 75 75 76 76 static pretty(str) { 77 77 // fix padding 78 - if (str.length % 4 > 0) 79 - str = (str + '===').slice(0, str.length + str.length % 4); 80 - // convert RFC 3548 to standard Base64 78 + let pad = 4 - str.length % 4; 79 + if (pad < 4) 80 + str += '==='.slice(0, pad); 81 + // convert Base64url (RFC 4648 section 5) to standard Base64 (RFC 4648 section 4) 81 82 str = str.replace(/-/g, '+').replace(/_/g, '/'); 82 83 // 80 column width 83 - return str.replace(/(.{80})/g, '$1\n'); 84 + return str.replace(/.{80}/g, '$&\n'); 84 85 } 85 86 86 87 static unarmor(a) {
+9 -16
context.js
··· 1 1 const 2 2 id = (elem) => document.getElementById(elem), 3 3 contextMenu = id('contextmenu'), 4 - btnHideTree = id('btnHideTree'), 5 4 btnCopyHex = id('btnCopyHex'), 6 5 btnCopyB64 = id('btnCopyB64'), 7 6 btnCopyTree = id('btnCopyTree'), ··· 11 10 const type = node.asn1.typeName(); 12 11 const valueEnabled = type != 'SET' && type != 'SEQUENCE'; 13 12 node.onclick = function (event) { 13 + // do not show the menu in case of clicking the icon 14 + if (event.srcElement.nodeName != 'SPAN') return; 14 15 contextMenu.style.left = event.pageX + 'px'; 15 16 contextMenu.style.top = event.pageY + 'px'; 16 17 contextMenu.style.visibility = 'visible'; 17 18 contextMenu.node = this; 18 - btnHideTree.innerText = (node.className == 'node') ? 'Hide subtree' : 'Show subtree'; 19 - btnHideTree.style.display = node.className.startsWith('node') ? 'block' : 'none'; 20 19 btnCopyValue.style.display = valueEnabled ? 'block' : 'none'; 20 + event.preventDefault(); 21 21 event.stopPropagation(); 22 22 }; 23 23 } 24 24 25 - function close() { 25 + function close(event) { 26 26 contextMenu.style.visibility = 'hidden'; 27 + event.stopPropagation(); 27 28 } 28 29 29 30 contextMenu.onmouseleave = close; 30 31 31 - btnHideTree.onclick = function (event) { 32 - event.stopPropagation(); 33 - const node = contextMenu.node; 34 - node.className = (node.className == 'node collapsed') ? 'node' : 'node collapsed'; 35 - close(); 36 - }; 37 - 38 32 btnCopyHex.onclick = function (event) { 39 - event.stopPropagation(); 40 33 navigator.clipboard.writeText(contextMenu.node.asn1.toHexString('byte')); 41 - close(); 34 + close(event); 42 35 }; 43 36 44 37 btnCopyB64.onclick = function (event) { 45 38 event.stopPropagation(); 46 39 navigator.clipboard.writeText(contextMenu.node.asn1.toB64String()); 47 - close(); 40 + close(event); 48 41 }; 49 42 50 43 btnCopyTree.onclick = function (event) { 51 44 event.stopPropagation(); 52 45 navigator.clipboard.writeText(contextMenu.node.asn1.toPrettyString()); 53 - close(); 46 + close(event); 54 47 }; 55 48 56 49 btnCopyValue.onclick = function (event) { 57 50 event.stopPropagation(); 58 51 navigator.clipboard.writeText(contextMenu.node.asn1.content()); 59 - close(); 52 + close(event); 60 53 };
+9 -6
defs.js
··· 1 1 // ASN.1 RFC definitions matcher 2 - // Copyright (c) 2023-2024 Lapo Luchini <lapo@lapo.it> 2 + // Copyright (c) 2023 Lapo Luchini <lapo@lapo.it> 3 3 4 4 // Permission to use, copy, modify, and/or distribute this software for any 5 5 // purpose with or without fee is hereby granted, provided that the above 6 6 // copyright notice and this permission notice appear in all copies. 7 - // 7 + // 8 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ··· 29 29 def = Object.assign({}, def); 30 30 def.type = Defs.searchType(name).type; 31 31 } 32 - if (def?.type?.name == 'CHOICE') { 33 - for (let c of def.type.content) { 32 + if (def?.name == 'CHOICE' || def?.type?.name == 'CHOICE') { 33 + for (let c of def.content ?? def.type.content) { 34 34 if (tn != c.type.name && tn != c.name) 35 35 c = translate(c); 36 36 if (tn == c.type.name || tn == c.name) { 37 37 def = Object.assign({}, def); 38 + if (c.id) def.id = c.id; 38 39 def.type = c.type.name ? c.type : c; 39 40 break; 40 41 } ··· 90 91 type = def.content[0]; 91 92 else { 92 93 let tn = subval.typeName().replaceAll('_', ' '); 93 - while (true) { 94 + for (;;) { 94 95 type = def.content[j++]; 95 96 if (!type || typeof type != 'object') break; 96 97 if (type?.type?.type) ··· 127 128 Defs.RFC = rfcdef; 128 129 129 130 Defs.commonTypes = [ 130 - [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ], 131 + [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ], 131 132 [ 'X.509 public key info', '1.3.6.1.5.5.7.0.18', 'SubjectPublicKeyInfo' ], 133 + [ 'X.509 certificate revocation list', '1.3.6.1.5.5.7.0.18', 'CertificateList' ], 132 134 [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ], 133 135 [ 'PKCS#1 RSA private key', '1.2.840.113549.1.1.0.1', 'RSAPrivateKey' ], 134 136 [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ], 135 137 [ 'PKCS#8 private key', '1.2.840.113549.1.8.1.1', 'PrivateKeyInfo' ], 136 138 [ 'PKCS#10 certification request', '1.2.840.113549.1.10.1.1', 'CertificationRequest' ], 137 139 [ 'CMP PKI Message', '1.3.6.1.5.5.7.0.16', 'PKIMessage' ], 140 + [ 'LDAP Message', '1.3.6.1.1.18', 'LDAPMessage' ], 138 141 ].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfcdef[arr[1]], arr[2]) }));
+27 -11
dom.js
··· 1 1 // ASN.1 JavaScript decoder 2 - // Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 2 + // Copyright (c) 2008 Lapo Luchini <lapo@lapo.it> 3 3 4 4 // Permission to use, copy, modify, and/or distribute this software for any 5 5 // purpose with or without fee is hereby granted, provided that the above 6 6 // copyright notice and this permission notice appear in all copies. 7 - // 7 + // 8 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ··· 24 24 ellipsis: '\u2026', 25 25 tag: function (tagName, className, text) { 26 26 let t = document.createElement(tagName); 27 - t.className = className; 27 + if (className) t.className = className; 28 28 if (text) t.innerText = text; 29 29 return t; 30 30 }, ··· 56 56 toDOM(spaces) { 57 57 spaces = spaces || ''; 58 58 let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D)); 59 - let node = DOM.tag('div', 'node'); 59 + let node = DOM.tag('li'); 60 60 node.asn1 = this; 61 61 let head = DOM.tag('span', 'head'); 62 - head.appendChild(DOM.tag('span', 'spaces', spaces)); 63 62 const typeName = this.typeName().replace(/_/g, ' '); 64 63 if (this.def) { 65 64 if (this.def.id) { ··· 111 110 content = content.replace(/</g, '&lt;'); 112 111 content = content.replace(/\n/g, '<br>'); 113 112 } 114 - node.appendChild(head); 115 - this.node = node; 113 + // add the li and details section for this node 114 + let contentNode; 115 + let childNode; 116 + if (this.sub !== null) { 117 + let details = DOM.tag('details'); 118 + details.setAttribute('open', ''); 119 + node.appendChild(details); 120 + let summary = DOM.tag('summary', 'node'); 121 + details.appendChild(summary); 122 + summary.appendChild(head); 123 + contentNode = summary; 124 + childNode = details; 125 + } else { 126 + contentNode = node; 127 + contentNode.classList.add('node'); 128 + contentNode.appendChild(head); 129 + } 130 + this.node = contentNode; 116 131 this.head = head; 117 132 let value = DOM.tag('div', 'value'); 118 133 let s = 'Offset: ' + this.stream.pos + '<br>'; ··· 135 150 } 136 151 } 137 152 value.innerHTML = s; 138 - node.appendChild(value); 139 - let sub = DOM.tag('div', 'sub'); 153 + contentNode.appendChild(value); 140 154 if (this.sub !== null) { 155 + let sub = DOM.tag('ul'); 156 + childNode.appendChild(sub); 141 157 spaces += '\xA0 '; 142 158 for (let i = 0, max = this.sub.length; i < max; ++i) 143 159 sub.appendChild(this.sub[i].toDOM(spaces)); 144 160 } 145 - node.appendChild(sub); 146 161 bindContextMenu(node); 147 162 return node; 148 163 } ··· 169 184 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; }; 170 185 this.head.onmouseout = function () { this.hexNode.className = 'hex'; }; 171 186 node.asn1 = this; 172 - node.onmouseover = function () { 187 + node.onmouseover = function (event) { 173 188 let current = !root.selected; 174 189 if (current) { 175 190 root.selected = this.asn1; 176 191 this.className = 'hexCurrent'; 177 192 } 178 193 this.asn1.fakeHover(current); 194 + event.stopPropagation(); 179 195 }; 180 196 node.onmouseout = function () { 181 197 let current = (root.selected == this.asn1);
+4
dumpASN1.js
··· 1 1 #!/usr/bin/env node 2 2 3 + // usage: 4 + // ./dumpASN1.js filename 5 + // ./dumpASN1.js data:base64,MDMCAQFjLgQACgEACgEAAgEAAgEAAQEAoA+jDQQFTnRWZXIEBAEAAAAwCgQITmV0bG9nb24=== 6 + 3 7 import * as fs from 'node:fs'; 4 8 import { Base64 } from './base64.js'; 5 9 import { ASN1 } from './asn1.js';
+9
examples/cms-password.p7m
··· 1 + This is a PKCS#7/CMS encrypted with passwod. 2 + $ echo content | openssl cms -encrypt -pwri_password test -aes256 -outform pem -out examples/cms-password.p7m 3 + -----BEGIN CMS----- 4 + MIHYBgkqhkiG9w0BBwOggcowgccCAQMxgYOjgYACAQCgGwYJKoZIhvcNAQUMMA4E 5 + CED/DSxXMtH6AgIIADAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEASoEEDIQbJMC 6 + Sfb3LpwHduj/meQEMKwrwq5M4V0stztm6OUTAsFY2zKDY20SApwSEeEcAh9TM42E 7 + 1palnHeqHTBpC8pIpjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBByt+scPrdM 8 + giR7WUOJyB3hgBDcD3UDMtZSep8X/3yy1/Yq 9 + -----END CMS-----
+12
examples/crl-rfc5280.b64
··· 1 + CRL example from RFC5280 as found here: 2 + https://csrc.nist.gov/projects/pki-testing/sample-certificates-and-crls 3 + 4 + begin-base64 644 crl-rfc5280.der 5 + MIIBYDCBygIBATANBgkqhkiG9w0BAQUFADBDMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZIm 6 + iZPyLGQBGRYHZXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQRcNMDUwMjA1MTIwMDAwWhcNMDUw 7 + MjA2MTIwMDAwWjAiMCACARIXDTA0MTExOTE1NTcwM1owDDAKBgNVHRUEAwoBAaAvMC0wHwYDVR0j 8 + BBgwFoAUCGivhTPIOUp6+IKTjnBqSiCELDIwCgYDVR0UBAMCAQwwDQYJKoZIhvcNAQEFBQADgYEA 9 + ItwYffcIzsx10NBqm60Q9HYjtIFutW2+DvsVFGzIF20f7pAXom9g5L2qjFXejoRvkvifEBInr0rU 10 + L4XiNkR9qqNMJTgV/wD9Pn7uPSYS69jnK2LiK8NGgO94gtEVxtCccmrLznrtZ5mLbnCBfUNCdMGm 11 + r8FVF6IzTNYGmCuk/C4= 12 + ====
+8
examples/ldapmessage.b64
··· 1 + LDAPMessage example as found on ldap.com. 2 + 3 + Original link: 4 + https://ldap.com/ldapv3-wire-protocol-reference-ldap-message/ 5 + 6 + begin-base64 644 ldapmessage.der 7 + MDUCAQVKEWRjPWV4YW1wbGUsZGM9Y29toB0wGwQWMS4yLjg0MC4xMTM1NTYuMS40LjgwNQEB/w== 8 + ====
+1 -2
favicon.svg
··· 1 - <?xml version="1.0" encoding="UTF-8"?> 2 - <svg width="192" height="192" version="1.1" viewBox="0 0 50.8 50.8" xmlns="http://www.w3.org/2000/svg"><rect width="50.8" height="50.8" stroke-width=".25275"/><g transform="translate(0 .23698)"><g transform="translate(0 -.30431)"><path transform="matrix(.26458 0 0 .26458 .72192 -3.1337)" d="m65.411 87.986h-31.354l-4.9479 14.167h-20.156l28.802-77.761h23.906l28.802 77.761h-20.156zm-26.354-14.427h21.302l-10.625-30.938zm125.83-46.719v16.458q-6.4063-2.8646-12.5-4.3229t-11.51-1.4583q-7.1875 0-10.625 1.9792t-3.4375 6.1459q0 3.125 2.2917 4.8958 2.3438 1.7188 8.4375 2.9688l8.5417 1.7188q12.969 2.6042 18.438 7.9167 5.4688 5.3125 5.4688 15.104 0 12.865-7.6563 19.167-7.6042 6.25-23.281 6.25-7.3958 0-14.844-1.4062-7.4479-1.4063-14.896-4.1667v-16.927q7.4479 3.9583 14.375 5.9896 6.9792 1.9792 13.438 1.9792 6.5625 0 10.052-2.1875t3.4896-6.25q0-3.6458-2.3958-5.625-2.3438-1.9792-9.4271-3.5417l-7.7604-1.7188q-11.667-2.5-17.083-7.9688-5.3646-5.4688-5.3646-14.74 0-11.615 7.5-17.865 7.5-6.25 21.563-6.25 6.4063 0 13.177 0.98959 6.7708 0.9375 14.01 2.8646z" fill="#58a6ff" aria-label="AS"/><g transform="matrix(.26458 0 0 .26458 2.807 20.959)" style="white-space:pre" aria-label="N1"><path d="m7.2136 24.392h22.396l28.281 53.334v-53.334h19.01v77.761h-22.396l-28.281-53.334v53.334h-19.01z" fill="#58a6ff"/><path d="m109.19 88.298h17.708v-50.261l-18.177 3.75v-13.646l18.073-3.75h19.063v63.906h17.708v13.854h-54.375z" fill="#fffe58"/></g></g></g></svg> 1 + <svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 50.8 50.8"><path d="M0 0h50.8v50.8H0z"/><path fill="#58a6ff" d="M18.028 20.078H9.733l-1.31 3.749H3.092l7.62-20.574h6.325l7.62 20.574h-5.332zm-6.972-3.817h5.636L13.88 8.076zm33.292-12.36v4.354q-1.695-.758-3.308-1.144t-3.045-.386q-1.902 0-2.811.524t-.91 1.626q0 .827.607 1.295.62.455 2.232.786l2.26.454q3.432.69 4.879 2.095t1.446 3.996q0 3.404-2.025 5.072-2.012 1.653-6.16 1.653-1.957 0-3.927-.372t-3.942-1.102v-4.479q1.971 1.047 3.804 1.585 1.846.523 3.555.523 1.737 0 2.66-.578t.923-1.654q0-.965-.634-1.488-.62-.524-2.494-.937l-2.053-.455q-3.087-.661-4.52-2.108-1.42-1.447-1.42-3.9 0-3.073 1.985-4.727 1.984-1.654 5.705-1.654 1.695 0 3.486.262 1.792.248 3.707.758z" aria-label="AS"/><g aria-label="N1" style="white-space:pre"><path fill="#58a6ff" d="M4.716 27.345h5.925l7.483 14.111v-14.11h5.03v20.573h-5.926L9.745 33.81v14.11h-5.03z"/><path fill="#fffe58" d="M31.696 44.254h4.686V30.955l-4.81.993v-3.61l4.782-.993h5.044v16.908h4.685v3.666H31.696z"/></g></svg>
+2 -2
hex.js
··· 1 1 // Hex JavaScript decoder 2 - // Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 2 + // Copyright (c) 2008 Lapo Luchini <lapo@lapo.it> 3 3 4 4 // Permission to use, copy, modify, and/or distribute this software for any 5 5 // purpose with or without fee is hereby granted, provided that the above 6 6 // copyright notice and this permission notice appear in all copies. 7 - // 7 + // 8 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-43
index-dark.css
··· 1 - :root { 2 - --main-bg-color: #0d1116; 3 - --main-text-color: #f8f8f2; 4 - --headline-text-color: #8be9fd; 5 - --button-border-color: #505050; 6 - --button-bg-color: #303030; 7 - --button-bghover-color: #404040; 8 - --input-border-color: #505050; 9 - --input-bg-color: #0c0e11; 10 - --link-color: #58a6ff; 11 - --link-hover-color: #9b9bea; 12 - --header-bg-color: #161b22; 13 - --page-bg-color: #000000; 14 - --license-bg-color: #4b4a4a; 15 - --license-border-color: black; 16 - --sub-border-color: #383838; 17 - --preview-bg-color: #989797; 18 - --preview-border-color: #b5b3b3; 19 - --dump-bg-color: #0c0e11; 20 - --dump-border-color: #505050; 21 - --hover-bg-color: #505050; 22 - } 23 - h1 { 24 - font-weight: 200; 25 - } 26 - .license .hidden { 27 - background-color: #4b4a4a; /*minimal support for IE11*/ 28 - background-color: var(--license-bg-color); 29 - } 30 - .value { 31 - background-color: #303030; /*minimal support for IE11*/ 32 - background-color: var(--button-bg-color); 33 - } 34 - #dump .tag { color: #58a6ff; } 35 - #dump .dlen { color: darkcyan; } 36 - #dump .ulen { color: #00b6b6; } 37 - #dump .intro { color: #58a6ff; } 38 - #dump .outro { color: #00b6b6; } 39 - #dump .skip { color: #707070; background-color: #222222; } 40 - #dump .hexCurrent { background-color: #727272; } 41 - #dump .hexCurrent .hex { background-color: #474747; } 42 - #dump .hexCurrent .tag { color: #6db0fc; } 43 - #dump .hexCurrent .dlen { color: #00b6b6; }
+157 -20
index.css
··· 1 - :root { 1 + html { 2 2 --main-bg-color: #C0C0C0; 3 3 --main-text-color: #000000; 4 + --id-text-color: #7d5900; 4 5 --headline-text-color: #8be9fd; 5 6 --button-border-color: #767676; 6 7 --button-bg-color: #efefef; ··· 18 19 --preview-border-color: #505050; 19 20 --dump-bg-color: #C0C0C0; 20 21 --dump-border-color: #E0E0E0; 22 + --dump-tag: blue; 23 + --dump-dlen: darkcyan; 24 + --dump-ulen: darkgreen; 25 + --dump-intro: blue; 26 + --dump-outro: darkgreen; 27 + --dump-skip: #666666; 28 + --dump-skip-bg: #C0C0C0; 29 + --dump-hex-current: #808080; 30 + --dump-hex-current-hex: #A0A0A0; 31 + --dump-hex-current-dlen: #004040; 21 32 --hover-bg-color: #E0E0E0; 33 + --tree-zoom-fix: -1px; 34 + --tree-line: #999; 35 + } 36 + html[data-theme="dark"] { 37 + --main-bg-color: #0d1116; 38 + --main-text-color: #f8f8f2; 39 + --id-text-color: #9d7c2d; 40 + --headline-text-color: #8be9fd; 41 + --button-border-color: #505050; 42 + --button-bg-color: #303030; 43 + --button-bghover-color: #404040; 44 + --input-border-color: #505050; 45 + --input-bg-color: #0c0e11; 46 + --link-color: #58a6ff; 47 + --link-hover-color: #9b9bea; 48 + --header-bg-color: #161b22; 49 + --page-bg-color: #000000; 50 + --license-bg-color: #4b4a4a; 51 + --license-border-color: black; 52 + --sub-border-color: #383838; 53 + --preview-bg-color: #989797; 54 + --preview-border-color: #b5b3b3; 55 + --dump-bg-color: #0c0e11; 56 + --dump-border-color: #505050; 57 + --dump-tag: #58a6ff; 58 + --dump-dlen: darkcyan; 59 + --dump-ulen: #00b6b6; 60 + --dump-intro: #58a6ff; 61 + --dump-outro: #00b6b6; 62 + --dump-skip: #707070; 63 + --dump-skip-bg: #222222; 64 + --dump-hex-current: #727272; 65 + --dump-hex-current-hex: #474747; 66 + --dump-hex-current-dlen: #00b6b6; 67 + --hover-bg-color: #505050; 68 + --tree-line: #333; 22 69 } 23 70 html, body { 24 71 background-color: var(--page-bg-color); ··· 84 131 #main-page { 85 132 background-color: var(--main-bg-color); 86 133 border: 0px; 87 - padding: 15px; 134 + padding: 5px; 88 135 } 89 136 #main-page > div { 90 137 position: relative; ··· 116 163 /*display: block;*/ 117 164 visibility: visible; 118 165 } 119 - .node { 120 - position: relative; 121 - } 122 - .sub { 123 - padding-left: 1.5em; 124 - border-left: solid 1px var(--sub-border-color); 125 - } 126 166 .head { 127 167 height: 1em; 128 168 white-space: nowrap; ··· 148 188 color: var(--preview-border-color); 149 189 } 150 190 .name.id { 151 - color: var(--main-text-color); 191 + color: var(--id-text-color); 152 192 } 153 193 .value { 154 194 display: none; 155 195 position: absolute; 156 196 z-index: 2; 157 197 top: 1.2em; 158 - left: 0; 198 + left: 30px; 159 199 background-color: #efefef; /*minimal support for IE11*/ 160 200 background-color: var(--button-bg-color); 161 201 border: solid 1px var(--button-border-color); ··· 193 233 white-space: pre; 194 234 padding: 5px; 195 235 } 196 - #dump .tag { color: blue; } 197 - #dump .dlen { color: darkcyan; } 198 - #dump .ulen { color: darkgreen; } 199 - #dump .intro { color: blue; } 200 - #dump .outro { color: darkgreen; } 201 - #dump .skip { color: #666666; background-color: #C0C0C0; } 202 - #dump .hexCurrent { background-color: #808080; } 203 - #dump .hexCurrent .hex { background-color: #A0A0A0; } 204 - #dump .hexCurrent .dlen { color: #004040; } 236 + #dump .tag { color: var(--dump-tag); } 237 + #dump .dlen { color: var(--dump-dlen); } 238 + #dump .ulen { color: var(--dump-ulen); } 239 + #dump .intro { color: var(--dump-intro); } 240 + #dump .outro { color: var(--dump-outro); } 241 + #dump .skip { color: var(--dump-skip); background-color: var(--dump-skip-bg); } 242 + #dump .hexCurrent { background-color: var(--dump-hex-current); } 243 + #dump .hexCurrent .hex { background-color: var(--dump-hex-current-hex); } 244 + #dump .hexCurrent .dlen { color: var(--dump-hex-current-dlen); } 205 245 #file { display: none; } 206 246 #area { width: 100%; } 207 247 #contextmenu { ··· 225 265 #contextmenu > button:hover { 226 266 background-color: var(--button-bghover-color); 227 267 } 268 + 269 + .treecollapse { 270 + --spacing: 1.5rem; 271 + --radius: 7px; 272 + padding-inline-start: 0px; 273 + } 274 + .treecollapse li{ 275 + display: block; 276 + position: relative; 277 + padding-left: calc(2 * var(--spacing) - var(--radius) - 2px); 278 + } 279 + .treecollapse ul{ 280 + padding-left: 0; 281 + margin-left: calc(var(--radius) - var(--spacing)); 282 + } 283 + .treecollapse ul li{ 284 + border-left: 1px solid var(--tree-line); 285 + } 286 + .treecollapse ul li:last-child{ 287 + border-color: transparent; 288 + } 289 + .treecollapse ul li::before{ 290 + content: ''; 291 + display: block; 292 + position: absolute; 293 + top: calc(var(--spacing) / -1.6); 294 + left: var(--tree-zoom-fix); 295 + width: calc(var(--spacing) + 2px); 296 + height: calc(var(--spacing) + 1px); 297 + border: solid var(--tree-line); 298 + border-width: 0 0 1px 1px; 299 + } 300 + .treecollapse summary{ 301 + display : block; 302 + cursor : pointer; 303 + } 304 + .treecollapse summary::marker, 305 + .treecollapse summary::-webkit-details-marker{ 306 + display : none; 307 + } 308 + .treecollapse summary:focus{ 309 + outline : none; 310 + } 311 + .treecollapse summary:focus-visible{ 312 + outline : 1px dotted #000; 313 + } 314 + .treecollapse summary::before{ 315 + content: ''; 316 + display: block; 317 + position: absolute; 318 + top: calc(var(--spacing) / 2 - var(--radius)); 319 + left: calc(var(--spacing) - var(--radius) - 1px); 320 + width: calc(2 * var(--radius)); 321 + height: calc(2 * var(--radius)); 322 + } 323 + .treecollapse summary::before{ 324 + z-index: 1; 325 + top: 1px; 326 + background: url('tree-icon-light.svg'); 327 + } 328 + html[data-theme="dark"] .treecollapse summary::before{ 329 + background: url('tree-icon-dark.svg'); 330 + } 331 + .treecollapse details[open] > summary::before{ 332 + background-position : calc(-2 * var(--radius)) 0; 333 + } 334 + 335 + /* 336 + Zoom fix to have straight lines in treeview 337 + Zoom level and dpi resolution: 338 + - 175%: 336dpi 339 + - 150%: 288dpi 340 + - 110%: 212dpi 341 + - 100%: 192dpi 342 + - 90%: 173dpi 343 + - 80%: 154dpi 344 + */ 345 + @media (resolution <= 154dpi) { 346 + :root{ 347 + --tree-zoom-fix: -0.6px; 348 + } 349 + } 350 + @media (155dpi <= resolution < 192dpi) { 351 + :root{ 352 + --tree-zoom-fix: -0.7px; 353 + } 354 + } 355 + @media (192dpi <= resolution < 336dpi) { 356 + :root{ 357 + --tree-zoom-fix: -1px; 358 + } 359 + } 360 + @media (336dpi <= resolution) { 361 + :root{ 362 + --tree-zoom-fix: -0.9px; 363 + } 364 + }
+14 -10
index.html
··· 1 1 <!DOCTYPE html> 2 - <html> 2 + <html data-theme="dark"> 3 3 <head> 4 - <meta charset="US-ASCII"> 4 + <meta charset="UTF-8"> 5 5 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 6 + <meta name="theme-color" content="#0d1116" media="(prefers-color-scheme: dark)"> 7 + <meta name="theme-color" content="#C0C0C0" media="(prefers-color-scheme: light)"> 6 8 <title>ASN.1 JavaScript decoder</title> 7 - <link rel="stylesheet" href="index.css" type="text/css" id="theme-base"> 9 + <link rel="stylesheet" href="index.css" type="text/css"> 8 10 <link rel="icon" type="image/svg+xml" sizes="192x192" href="favicon.svg"> 9 11 </head> 10 12 <body> 11 13 <div id="contextmenu"> 12 - <button id="btnHideTree">Hide subtree</button> 13 14 <button id="btnCopyHex">Copy hex dump</button> 14 15 <button id="btnCopyB64">Copy Base64</button> 15 16 <button id="btnCopyTree">Copy subtree</button> ··· 36 37 <div id="tree"></div> 37 38 </div> 38 39 <form> 39 - <textarea id="area" rows="8"></textarea> 40 + <textarea id="area" rows="8" placeholder="Paste hex or base64 or PEM encoded ASN.1 BER or DER structures here, or load a file."></textarea> 40 41 <br> 41 42 <br> 42 43 <label title="can be slow with big files"><input type="checkbox" id="wantHex" checked="checked"> with hex dump</label> ··· 46 47 <br><br> 47 48 <table> 48 49 <tr><td>Drag or load file:</td><td><input type="file" id="file"></td></tr> 49 - <tr><td>Load examples:</td><td> 50 + <tr id="rowExamples"><td>Load examples:</td><td> 50 51 <select id="examples"> 51 52 <option value="sig-p256-der.p7m">PKCS#7/CMS attached signature (DER)</option> 52 53 <option value="sig-p256-ber.p7m">PKCS#7/CMS attached signature (BER)</option> 53 54 <option value="sig-rsa1024-sha1.p7s">PKCS#7/CMS detached signature (old)</option> 55 + <option value="cms-password.p7m">PKCS#7/CMS encrypted with password</option> 54 56 <option value="letsencrypt-x3.cer">X.509 certificate: Let's Encrypt X3</option> 55 57 <option value="ed25519.cer">X.509 certificate: ed25519 (RFC 8410)</option> 56 58 <option value="pkcs1.pem">PKCS#1 RSA key (RFC 8017)</option> 57 59 <option value="pkcs8-rsa.pem">PKCS#8 RSA key (RFC 5208)</option> 58 60 <option value="pkcs10.pem">PKCS#10 certification request (RFC 2986)</option> 61 + <option value="crl-rfc5280.b64">CRL example (RFC 5280)</option> 59 62 <option value="cmpv2.b64">CMP PKI message (RFC 4210)</option> 63 + <option value="ldapmessage.b64">LDAP message (RFC 4511)</option> 60 64 </select> 61 65 <input id="butExample" type="button" value="load"><br> 62 66 </td></tr> ··· 68 72 <div id="help"> 69 73 <h2>Instructions</h2> 70 74 <p>This page contains a JavaScript generic ASN.1 parser that can decode any valid ASN.1 DER or BER structure whether Base64-encoded (raw base64, PEM armoring and <span class="tt">begin-base64</span> are recognized) or Hex-encoded. </p> 71 - <p>This tool can be used online at the address <a href="https://asn1js.eu/"><span class="tt">https://asn1js.eu/</span></a> or offline, unpacking <a href="https://asn1js.eu/asn1js.zip">the ZIP file</a> in a directory and opening <span class="tt">index.html</span> in a browser</p> 75 + <p>This tool can be used online at the address <a href="https://asn1js.eu/"><span class="tt">https://asn1js.eu/</span></a> or offline, unpacking <a href="https://asn1js.eu/asn1js.zip">the ZIP file</a> in a directory and opening <span class="tt">index-local.html</span> in a browser.</p> 72 76 <p>On the left of the page will be printed a tree representing the hierarchical structure, on the right side an hex dump will be shown. <br> 73 77 Hovering on the tree highlights ancestry (the hovered node and all its ancestors get colored) and the position of the hovered node gets highlighted in the hex dump (with header and content in a different colors). <br> 74 78 Clicking a node in the tree will hide its sub-nodes (collapsed nodes can be noticed because they will become <i>italic</i>).</p> ··· 78 82 <h3>Copyright</h3> 79 83 <div><p class="hidden"> 80 84 ASN.1 JavaScript decoder<br> 81 - Copyright &copy; 2008-2024 Lapo Luchini <a href="mailto:lapo@lapo.it?subject=ASN1js">&lt;lapo@lapo.it&gt;</a><br> 85 + Copyright &copy; 2008-2025 Lapo Luchini <a href="mailto:lapo@lapo.it?subject=ASN1js">&lt;lapo@lapo.it&gt;</a><br> 82 86 <br> 83 87 Permission to use, copy, modify, and/or distribute this software for any 84 88 purpose with or without fee is hereby granted, provided that the above ··· 92 96 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 93 97 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 94 98 </p></div> 95 - <p>ASN.1 JavaScript decoder Copyright &copy; 2008-2024 <a href="https://lapo.it/">Lapo Luchini</a>; released as <a href="https://opensource.org/licenses/isc-license.txt">opensource</a> under the <a href="https://en.wikipedia.org/wiki/ISC_licence">ISC license</a>.</p> 99 + <p>ASN.1 JavaScript decoder Copyright &copy; 2008-2025 <a href="https://lapo.it/">Lapo Luchini</a>; released as <a href="https://opensource.org/licenses/isc-license.txt">opensource</a> under the <a href="https://en.wikipedia.org/wiki/ISC_licence">ISC license</a>.</p> 96 100 </div> 97 101 <p><span class="tt">OBJECT&nbsp;IDENTIFIER</span> values are recognized using data taken from Peter Gutmann's <a href="https://www.cs.auckland.ac.nz/~pgut001/#standards">dumpasn1</a> program.</p> 98 102 <h3>Links</h3> 99 103 <ul> 100 104 <li><a href="https://asn1js.eu/">official website</a></li> 101 105 <li><a href="https://lapo.it/asn1js/">alternate website</a></li> 106 + <li><a href="https://asn1js.eu/index-local.html">single-file version working locally</a> (just save this link)</li> 102 107 <li>previous versions on githack: <select id="tags"><option>[select tag]</option></select></li> 103 108 <li><a href="http://idf.lapo.it/p/asn1js/">InDefero tracker</a> (currently offline)</li> 104 109 <li><a href="https://github.com/lapo-luchini/asn1js">github mirror</a></li> 105 110 <li><a href="https://www.openhub.net/p/asn1js">OpenHub code stats</a></li> 106 111 </ul> 107 112 </div> 108 - 109 113 <script type="module" src="index.js"></script> 110 114 </body> 111 115 </html>
+39 -56
index.js
··· 1 + import './theme.js'; 1 2 import { ASN1DOM } from './dom.js'; 2 3 import { Base64 } from './base64.js'; 3 4 import { Hex } from './hex.js'; ··· 15 16 area = id('area'), 16 17 file = id('file'), 17 18 examples = id('examples'), 18 - selectTheme = id('theme-select'), 19 19 selectDefs = id('definitions'), 20 20 selectTag = id('tags'); 21 21 ··· 41 41 function show(asn1) { 42 42 tree.innerHTML = ''; 43 43 dump.innerHTML = ''; 44 - tree.appendChild(asn1.toDOM()); 44 + let ul = document.createElement('ul'); 45 + ul.className = 'treecollapse'; 46 + tree.appendChild(ul); 47 + ul.appendChild(asn1.toDOM()); 45 48 if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked)); 46 49 } 47 - function decode(der, offset) { 50 + export function decode(der, offset) { 48 51 offset = offset || 0; 49 52 try { 50 53 const asn1 = ASN1DOM.decode(der, offset); ··· 103 106 text(tree, e); 104 107 } 105 108 } 106 - function decodeText(val) { 109 + export function decodeText(val) { 107 110 try { 108 111 let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val); 109 112 decode(der); ··· 112 115 dump.innerHTML = ''; 113 116 } 114 117 } 115 - function decodeBinaryString(str) { 118 + export function decodeBinaryString(str) { 116 119 let der; 117 120 try { 118 121 if (reHex.test(str)) der = Hex.decode(str); ··· 125 128 } 126 129 } 127 130 // set up buttons 128 - id('butDecode').onclick = function () { 129 - decodeText(area.value); 131 + const butClickHandlers = { 132 + butDecode: () => { 133 + decodeText(area.value); 134 + }, 135 + butClear: () => { 136 + area.value = ''; 137 + file.value = ''; 138 + tree.innerHTML = ''; 139 + dump.innerHTML = ''; 140 + selectDefs.innerHTML = ''; 141 + hash = window.location.hash = ''; 142 + }, 143 + butExample: () => { 144 + console.log('Loading example:', examples.value); 145 + let request = new XMLHttpRequest(); 146 + request.open('GET', 'examples/' + examples.value, true); 147 + request.onreadystatechange = function () { 148 + if (this.readyState !== 4) return; 149 + if (this.status >= 200 && this.status < 400) { 150 + area.value = this.responseText; 151 + decodeText(this.responseText); 152 + } else { 153 + console.log('Error loading example.'); 154 + } 155 + }; 156 + request.send(); 157 + }, 130 158 }; 131 - id('butClear').onclick = function () { 132 - area.value = ''; 133 - file.value = ''; 134 - tree.innerHTML = ''; 135 - dump.innerHTML = ''; 136 - selectDefs.innerHTML = ''; 137 - hash = window.location.hash = ''; 138 - }; 139 - id('butExample').onclick = function () { 140 - console.log('Loading example:', examples.value); 141 - let request = new XMLHttpRequest(); 142 - request.open('GET', 'examples/' + examples.value, true); 143 - request.onreadystatechange = function () { 144 - if (this.readyState !== 4) return; 145 - if (this.status >= 200 && this.status < 400) { 146 - area.value = this.responseText; 147 - decodeText(this.responseText); 148 - } else { 149 - console.log('Error loading example.'); 150 - } 151 - }; 152 - request.send(); 153 - }; 154 - // set dark theme depending on OS settings 155 - function setTheme() { 156 - let storedTheme = localStorage.getItem('theme'); 157 - let theme = 'os'; 158 - if (storedTheme) 159 - theme = storedTheme; 160 - selectTheme.value = theme; 161 - if (theme == 'os') { 162 - let prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); 163 - theme = prefersDarkScheme.matches ? 'dark': 'light'; 164 - } 165 - if (theme == 'dark') { 166 - const css1 = id('theme-base'); 167 - const css2 = css1.cloneNode(); 168 - css2.id = 'theme-override'; 169 - css2.href = 'index-' + theme + '.css'; 170 - css1.parentElement.appendChild(css2); 171 - } else { 172 - const css2 = id('theme-override'); 173 - if (css2) css2.remove(); 174 - } 159 + for (const [name, onClick] of Object.entries(butClickHandlers)) { 160 + let elem = id(name); 161 + if (elem) 162 + elem.onclick = onClick; 175 163 } 176 - setTheme(); 177 - selectTheme.addEventListener('change', function () { 178 - localStorage.setItem('theme', selectTheme.value); 179 - setTheme(); 180 - }); 181 164 // this is only used if window.FileReader 182 165 function read(f) { 183 166 area.value = ''; // clear text area, will get b64 content
+15 -9
int10.js
··· 1 1 // Big integer base-10 printing library 2 - // Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> 2 + // Copyright (c) 2008 Lapo Luchini <lapo@lapo.it> 3 3 4 4 // Permission to use, copy, modify, and/or distribute this software for any 5 5 // purpose with or without fee is hereby granted, provided that the above 6 6 // copyright notice and this permission notice appear in all copies. 7 - // 7 + // 8 8 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 9 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 10 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ··· 13 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 15 16 - let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 16 + /** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */ 17 + const max = 10000000000000; 17 18 18 19 export class Int10 { 19 20 /** ··· 26 27 27 28 /** 28 29 * Multiply value by m and add c. 29 - * @param {number} m - multiplier, must be < =256 30 - * @param {number} c - value to add 30 + * @param {number} m - multiplier, must be 0<m<=256 31 + * @param {number} c - value to add, must be c>=0 31 32 */ 32 33 mulAdd(m, c) { 34 + // assert(m > 0) 33 35 // assert(m <= 256) 36 + // assert(c >= 0) 34 37 let b = this.buf, 35 38 l = b.length, 36 39 i, t; ··· 71 74 72 75 /** 73 76 * Convert to decimal string representation. 74 - * @param {*} base - optional value, only value accepted is 10 77 + * @param {number} [base=10] - optional value, only value accepted is 10 78 + * @returns {string} The decimal string representation. 75 79 */ 76 - toString(base) { 77 - if ((base || 10) != 10) 78 - throw 'only base 10 is supported'; 80 + toString(base = 10) { 81 + if (base != 10) 82 + throw new Error('only base 10 is supported'); 79 83 let b = this.buf, 80 84 s = b[b.length - 1].toString(); 81 85 for (let i = b.length - 2; i >= 0; --i) ··· 86 90 /** 87 91 * Convert to Number value representation. 88 92 * Will probably overflow 2^53 and thus become approximate. 93 + * @returns {number} The numeric value. 89 94 */ 90 95 valueOf() { 91 96 let b = this.buf, ··· 97 102 98 103 /** 99 104 * Return value as a simple Number (if it is <= 10000000000000), or return this. 105 + * @returns {number | Int10} The simplified value. 100 106 */ 101 107 simplify() { 102 108 let b = this.buf;
+24 -5
package.json
··· 1 1 { 2 2 "name": "@lapo/asn1js", 3 - "version": "2.0.2", 3 + "version": "2.0.6", 4 4 "description": "Generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.", 5 5 "type": "module", 6 6 "main": "asn1.js", ··· 13 13 "license": "ISC", 14 14 "bugs": { "url": "https://github.com/lapo-luchini/asn1js/issues" }, 15 15 "homepage": "https://lapo.it/asn1js/", 16 - "files": [ "asn1.js", "base64.js", "hex.js", "int10.js", "oids.js" ], 16 + "files": [ "asn1.js", "base64.js", "hex.js", "int10.js", "dom.js", "defs.js", "oids.js", "rfcdef.js", "dumpASN1.js" ], 17 17 "scripts": { 18 - "lint": "npx eslint asn1.js base64.js hex.js int10.js oids.js tags.js context.js index.js parseRFC.js dumpASN1.js test.js testDefs.js", 18 + "lint": "npx eslint asn1.js base64.js hex.js int10.js dom.js defs.js oids.js rfcdef.js tags.js context.js index.js parseRFC.js dumpASN1.js test.js testDefs.js vite.config.js theme.js", 19 19 "lint-action": "npx @action-validator/cli .github/workflows/node.js.yml", 20 - "serve": "echo 'Connect to http://localhost:3000/' ; npx statik --port 3000 .", 20 + "build": "vite build", 21 + "serve": "npx -p local-web-server ws", 21 22 "test": "node test", 22 23 "testdefs": "node testDefs" 24 + }, 25 + "bin": { 26 + "dumpASN1": "./dumpASN1.js" 23 27 }, 24 28 "engines": { 25 29 "node": ">=12.20.0" 26 30 }, 27 31 "devDependencies": { 28 - "eslint": "^8.34.0" 32 + "@rollup/wasm-node": "^4.25.0", 33 + "eslint": "^8.57.1", 34 + "htmlparser2": "^9.1.0", 35 + "vite": "^5.4.10", 36 + "vite-plugin-dom": "^1.0.4", 37 + "vite-plugin-singlefile": "^2.0.3" 38 + }, 39 + "overrides": { 40 + "rollup": "npm:@rollup/wasm-node" 41 + }, 42 + "pnpm": { 43 + "overrides": { 44 + "rollup": "npm:@rollup/wasm-node" 45 + } 29 46 }, 30 47 "eslintConfig": { 31 48 "env": { ··· 44 61 "rules": { 45 62 "strict": [ "error", "function" ], 46 63 "indent": [ "error", 4 ], 64 + "no-trailing-spaces": [ "error" ], 47 65 "linebreak-style": [ "error", "unix" ], 66 + "eol-last": [ "error", "always" ], 48 67 "semi": [ "warn", "always" ], 49 68 "quotes": [ "error", "single", { "avoidEscape": true } ], 50 69 "no-var": [ "warn" ],
+51 -3
parseRFC.js
··· 1 1 #! /usr/bin/env node 2 2 3 + // RFC ASN.1 definition parser 4 + // Copyright (c) 2021 Lapo Luchini <lapo@lapo.it> 5 + 6 + // Permission to use, copy, modify, and/or distribute this software for any 7 + // purpose with or without fee is hereby granted, provided that the above 8 + // copyright notice and this permission notice appear in all copies. 9 + // 10 + // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 + // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 + // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 + // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 + // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 + // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 + // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 + 3 18 import * as fs from 'node:fs'; 4 19 5 20 const ··· 56 71 [ /emptyString {4}EncodingParameters ::= ''H/g, '' ], 57 72 [ /[(]CONSTRAINED BY[^)]+[)]/g, '' ], 58 73 ], 74 + 4511: [ 75 + [ /^\s+-- .*\r?\n/mg, '' ], // comments 76 + [ 'EXTENSIBILITY IMPLIED', '' ], 77 + [ /\.\.\.(,| {2})/g, '' ], 78 + [ /value AttributeValue/g, 'AttributeValue' ], 79 + [ /control Control/g, 'Control' ], 80 + [ /Attribute ::= PartialAttribute\(WITH COMPONENTS \{[^}]+\}\)/g, 'PartialAttribute ::= SEQUENCE { type AttributeDescription, vals SET SIZE (1..MAX) OF AttributeValue }' ], 81 + [ /,\s+\}/g, '}' ], 82 + [ /SaslCredentials,/g, 'SaslCredentials' ], 83 + [ /(BindResponse|ExtendedResponse) ::= \[APPLICATION [0-9]+\] SEQUENCE \{[^}]+\}/g, '$1 ::= ANY' ], 84 + [ /selector LDAPString/g, 'LDAPString' ], 85 + [ /filter Filter/g, 'Filter' ], 86 + [ /MatchingRuleAssertion,/g, 'MatchingRuleAssertion' ], 87 + [ /OF substring CHOICE/g, 'OF CHOICE' ], 88 + [ /partialAttribute PartialAttribute/g, 'PartialAttribute' ], 89 + [ /uri URI/g, 'URI' ], 90 + [ /OF change SEQUENCE/g, 'OF SEQUENCE' ], 91 + [ /attribute Attribute/g, 'Attribute' ], 92 + ], 59 93 }; 60 94 61 95 // const reWhitespace = /(?:\s|--(?:[}-]?[^\n}-])*(?:\n|--))*/y; ··· 309 343 let plicit = this.getRegEx('explicit/implicit', reTagType); 310 344 if (plicit == '') plicit = currentMod.tagDefault; 311 345 let x = this.parseType(); 346 + let name; 347 + switch (tagClass) { // keep in sync with ASN1.typeName 348 + case 'APPLICATION': 349 + name = 'Application ' + t; 350 + break; 351 + case 'PRIVATE': 352 + name = 'Private ' + t; 353 + break; 354 + case 'CONTEXT': 355 + // fall through 356 + default: 357 + name = '[' + t + ']'; 358 + break; 359 + } 312 360 return { 313 - name: '[' + t + ']', 361 + name, 314 362 type: 'tag', 315 363 'class': tagClass, 316 364 explicit: (plicit == 'EXPLICIT'), ··· 519 567 asn1[currentMod.oid] = currentMod; 520 568 } 521 569 /*asn1 = Object.keys(asn1).sort().reduce( 522 - (obj, key) => { 570 + (obj, key) => { 523 571 obj[key] = asn1[key]; 524 572 return obj; 525 - }, 573 + }, 526 574 {} 527 575 );*/ 528 576 fs.writeFileSync(process.argv[3], JSON.stringify(asn1, null, 2) + '\n', 'utf8');
+7 -3
release.sh
··· 1 1 #!/bin/sh 2 2 set -e 3 3 FILES=" 4 - asn1.js oids.js defs.js base64.js hex.js int10.js dom.js rfcdef.js test.js tags.js 5 - context.js index.css index-dark.css index.js index.html favicon.svg 4 + asn1.js oids.js defs.js base64.js hex.js int10.js dom.js context.js theme.js 5 + rfcdef.js test.js tags.js 6 + index.html index.css index.js index-local.html 7 + favicon.svg tree-icon-light.svg tree-icon-dark.svg 6 8 README.md LICENSE 7 9 updateOID.sh check.sh 8 10 examples ··· 26 28 ' > tags.js 27 29 chmod 644 examples/* 28 30 type gsha256sum >/dev/null 2>/dev/null && SHA256=gsha256sum || SHA256=sha256sum 29 - $SHA256 -t $FILES | gpg --clearsign > sha256sums.asc 31 + pnpm build 32 + cp dist/index.html index-local.html 33 + $SHA256 -t $FILES examples/* | gpg --clearsign > sha256sums.asc 30 34 7z a -tzip -mx=9 asn1js.zip $FILES sha256sums.asc 31 35 rsync -Pvrtz asn1js.zip $FILES lapo.it:www/asn1js/
+1503 -3
rfcdef.js
··· 1 - // content parsed from ASN.1 definitions as found in the following RFCs: 5280 5208 3369 3161 2986 4211 4210 8017 1 + // content parsed from ASN.1 definitions as found in the following RFCs: 5280 5208 3369 3161 2986 4211 4210 8017 4511 2 2 // Copyright (C) The IETF Trust (2008) 3 3 // as far as I can tell this file is allowed under the following clause: 4 4 // It is acceptable under the current IETF rules (RFC 5378) to modify extracted code if necessary. ··· 1983 1983 "CountryName": { 1984 1984 "name": "CountryName", 1985 1985 "type": { 1986 - "name": "[1]", 1986 + "name": "Application 1", 1987 1987 "type": "tag", 1988 1988 "class": "APPLICATION", 1989 1989 "explicit": true, ··· 2015 2015 "AdministrationDomainName": { 2016 2016 "name": "AdministrationDomainName", 2017 2017 "type": { 2018 - "name": "[2]", 2018 + "name": "Application 2", 2019 2019 "type": "tag", 2020 2020 "class": "APPLICATION", 2021 2021 "explicit": true, ··· 10369 10369 "type": { 10370 10370 "name": "AlgorithmIdentifier", 10371 10371 "type": "defined" 10372 + } 10373 + } 10374 + } 10375 + }, 10376 + "1.3.6.1.1.18": { 10377 + "name": "Lightweight-Directory-Access-Protocol-V3", 10378 + "oid": "1.3.6.1.1.18", 10379 + "source": "rfc4511.txt", 10380 + "tagDefault": "IMPLICIT", 10381 + "values": { 10382 + "maxInt": { 10383 + "name": "maxInt", 10384 + "type": { 10385 + "name": "INTEGER", 10386 + "type": "builtin" 10387 + }, 10388 + "value": 2147483647 10389 + } 10390 + }, 10391 + "types": { 10392 + "LDAPMessage": { 10393 + "name": "LDAPMessage", 10394 + "type": { 10395 + "name": "SEQUENCE", 10396 + "type": "builtin", 10397 + "content": [ 10398 + { 10399 + "id": "messageID", 10400 + "name": "MessageID", 10401 + "type": "defined" 10402 + }, 10403 + { 10404 + "id": "protocolOp", 10405 + "name": "CHOICE", 10406 + "type": "builtin", 10407 + "content": [ 10408 + { 10409 + "id": "bindRequest", 10410 + "name": "BindRequest", 10411 + "type": "defined" 10412 + }, 10413 + { 10414 + "id": "bindResponse", 10415 + "name": "BindResponse", 10416 + "type": "defined" 10417 + }, 10418 + { 10419 + "id": "unbindRequest", 10420 + "name": "UnbindRequest", 10421 + "type": "defined" 10422 + }, 10423 + { 10424 + "id": "searchRequest", 10425 + "name": "SearchRequest", 10426 + "type": "defined" 10427 + }, 10428 + { 10429 + "id": "searchResEntry", 10430 + "name": "SearchResultEntry", 10431 + "type": "defined" 10432 + }, 10433 + { 10434 + "id": "searchResDone", 10435 + "name": "SearchResultDone", 10436 + "type": "defined" 10437 + }, 10438 + { 10439 + "id": "searchResRef", 10440 + "name": "SearchResultReference", 10441 + "type": "defined" 10442 + }, 10443 + { 10444 + "id": "modifyRequest", 10445 + "name": "ModifyRequest", 10446 + "type": "defined" 10447 + }, 10448 + { 10449 + "id": "modifyResponse", 10450 + "name": "ModifyResponse", 10451 + "type": "defined" 10452 + }, 10453 + { 10454 + "id": "addRequest", 10455 + "name": "AddRequest", 10456 + "type": "defined" 10457 + }, 10458 + { 10459 + "id": "addResponse", 10460 + "name": "AddResponse", 10461 + "type": "defined" 10462 + }, 10463 + { 10464 + "id": "delRequest", 10465 + "name": "DelRequest", 10466 + "type": "defined" 10467 + }, 10468 + { 10469 + "id": "delResponse", 10470 + "name": "DelResponse", 10471 + "type": "defined" 10472 + }, 10473 + { 10474 + "id": "modDNRequest", 10475 + "name": "ModifyDNRequest", 10476 + "type": "defined" 10477 + }, 10478 + { 10479 + "id": "modDNResponse", 10480 + "name": "ModifyDNResponse", 10481 + "type": "defined" 10482 + }, 10483 + { 10484 + "id": "compareRequest", 10485 + "name": "CompareRequest", 10486 + "type": "defined" 10487 + }, 10488 + { 10489 + "id": "compareResponse", 10490 + "name": "CompareResponse", 10491 + "type": "defined" 10492 + }, 10493 + { 10494 + "id": "abandonRequest", 10495 + "name": "AbandonRequest", 10496 + "type": "defined" 10497 + }, 10498 + { 10499 + "id": "extendedReq", 10500 + "name": "ExtendedRequest", 10501 + "type": "defined" 10502 + }, 10503 + { 10504 + "id": "extendedResp", 10505 + "name": "ExtendedResponse", 10506 + "type": "defined" 10507 + }, 10508 + { 10509 + "id": "intermediateResponse", 10510 + "name": "IntermediateResponse", 10511 + "type": "defined" 10512 + } 10513 + ] 10514 + }, 10515 + { 10516 + "id": "controls", 10517 + "name": "[0]", 10518 + "type": "tag", 10519 + "class": "CONTEXT", 10520 + "explicit": false, 10521 + "content": [ 10522 + { 10523 + "name": "", 10524 + "type": { 10525 + "name": "Controls", 10526 + "type": "defined" 10527 + } 10528 + } 10529 + ], 10530 + "optional": true 10531 + } 10532 + ] 10533 + } 10534 + }, 10535 + "MessageID": { 10536 + "name": "MessageID", 10537 + "type": { 10538 + "name": "INTEGER", 10539 + "type": "builtin", 10540 + "range": [ 10541 + 0, 10542 + "maxInt" 10543 + ] 10544 + } 10545 + }, 10546 + "LDAPString": { 10547 + "name": "LDAPString", 10548 + "type": { 10549 + "name": "OCTET STRING", 10550 + "type": "builtin" 10551 + } 10552 + }, 10553 + "LDAPOID": { 10554 + "name": "LDAPOID", 10555 + "type": { 10556 + "name": "OCTET STRING", 10557 + "type": "builtin" 10558 + } 10559 + }, 10560 + "LDAPDN": { 10561 + "name": "LDAPDN", 10562 + "type": { 10563 + "name": "LDAPString", 10564 + "type": "defined" 10565 + } 10566 + }, 10567 + "RelativeLDAPDN": { 10568 + "name": "RelativeLDAPDN", 10569 + "type": { 10570 + "name": "LDAPString", 10571 + "type": "defined" 10572 + } 10573 + }, 10574 + "AttributeDescription": { 10575 + "name": "AttributeDescription", 10576 + "type": { 10577 + "name": "LDAPString", 10578 + "type": "defined" 10579 + } 10580 + }, 10581 + "AttributeValue": { 10582 + "name": "AttributeValue", 10583 + "type": { 10584 + "name": "OCTET STRING", 10585 + "type": "builtin" 10586 + } 10587 + }, 10588 + "AttributeValueAssertion": { 10589 + "name": "AttributeValueAssertion", 10590 + "type": { 10591 + "name": "SEQUENCE", 10592 + "type": "builtin", 10593 + "content": [ 10594 + { 10595 + "id": "attributeDesc", 10596 + "name": "AttributeDescription", 10597 + "type": "defined" 10598 + }, 10599 + { 10600 + "id": "assertionValue", 10601 + "name": "AssertionValue", 10602 + "type": "defined" 10603 + } 10604 + ] 10605 + } 10606 + }, 10607 + "AssertionValue": { 10608 + "name": "AssertionValue", 10609 + "type": { 10610 + "name": "OCTET STRING", 10611 + "type": "builtin" 10612 + } 10613 + }, 10614 + "PartialAttribute": { 10615 + "name": "PartialAttribute", 10616 + "type": { 10617 + "name": "SEQUENCE", 10618 + "type": "builtin", 10619 + "content": [ 10620 + { 10621 + "id": "type", 10622 + "name": "AttributeDescription", 10623 + "type": "defined" 10624 + }, 10625 + { 10626 + "id": "vals", 10627 + "name": "SET", 10628 + "type": "builtin", 10629 + "typeOf": 1, 10630 + "size": [ 10631 + 1, 10632 + "MAX" 10633 + ], 10634 + "content": [ 10635 + { 10636 + "name": "AttributeValue", 10637 + "type": "defined" 10638 + } 10639 + ] 10640 + } 10641 + ] 10642 + } 10643 + }, 10644 + "MatchingRuleId": { 10645 + "name": "MatchingRuleId", 10646 + "type": { 10647 + "name": "LDAPString", 10648 + "type": "defined" 10649 + } 10650 + }, 10651 + "LDAPResult": { 10652 + "name": "LDAPResult", 10653 + "type": { 10654 + "name": "SEQUENCE", 10655 + "type": "builtin", 10656 + "content": [ 10657 + { 10658 + "id": "resultCode", 10659 + "name": "ENUMERATED", 10660 + "type": "builtin", 10661 + "content": { 10662 + "success": 0, 10663 + "operationsError": 1, 10664 + "protocolError": 2, 10665 + "timeLimitExceeded": 3, 10666 + "sizeLimitExceeded": 4, 10667 + "compareFalse": 5, 10668 + "compareTrue": 6, 10669 + "authMethodNotSupported": 7, 10670 + "strongerAuthRequired": 8, 10671 + "referral": 10, 10672 + "adminLimitExceeded": 11, 10673 + "unavailableCriticalExtension": 12, 10674 + "confidentialityRequired": 13, 10675 + "saslBindInProgress": 14, 10676 + "noSuchAttribute": 16, 10677 + "undefinedAttributeType": 17, 10678 + "inappropriateMatching": 18, 10679 + "constraintViolation": 19, 10680 + "attributeOrValueExists": 20, 10681 + "invalidAttributeSyntax": 21, 10682 + "noSuchObject": 32, 10683 + "aliasProblem": 33, 10684 + "invalidDNSyntax": 34, 10685 + "aliasDereferencingProblem": 36, 10686 + "inappropriateAuthentication": 48, 10687 + "invalidCredentials": 49, 10688 + "insufficientAccessRights": 50, 10689 + "busy": 51, 10690 + "unavailable": 52, 10691 + "unwillingToPerform": 53, 10692 + "loopDetect": 54, 10693 + "namingViolation": 64, 10694 + "objectClassViolation": 65, 10695 + "notAllowedOnNonLeaf": 66, 10696 + "notAllowedOnRDN": 67, 10697 + "entryAlreadyExists": 68, 10698 + "objectClassModsProhibited": 69, 10699 + "affectsMultipleDSAs": 71, 10700 + "other": 80 10701 + } 10702 + }, 10703 + { 10704 + "id": "matchedDN", 10705 + "name": "LDAPDN", 10706 + "type": "defined" 10707 + }, 10708 + { 10709 + "id": "diagnosticMessage", 10710 + "name": "LDAPString", 10711 + "type": "defined" 10712 + }, 10713 + { 10714 + "id": "referral", 10715 + "name": "[3]", 10716 + "type": "tag", 10717 + "class": "CONTEXT", 10718 + "explicit": false, 10719 + "content": [ 10720 + { 10721 + "name": "", 10722 + "type": { 10723 + "name": "Referral", 10724 + "type": "defined" 10725 + } 10726 + } 10727 + ], 10728 + "optional": true 10729 + } 10730 + ] 10731 + } 10732 + }, 10733 + "Referral": { 10734 + "name": "Referral", 10735 + "type": { 10736 + "name": "SEQUENCE", 10737 + "type": "builtin", 10738 + "typeOf": 1, 10739 + "size": [ 10740 + 1, 10741 + "MAX" 10742 + ], 10743 + "content": [ 10744 + { 10745 + "name": "URI", 10746 + "type": "defined" 10747 + } 10748 + ] 10749 + } 10750 + }, 10751 + "URI": { 10752 + "name": "URI", 10753 + "type": { 10754 + "name": "LDAPString", 10755 + "type": "defined" 10756 + } 10757 + }, 10758 + "Controls": { 10759 + "name": "Controls", 10760 + "type": { 10761 + "name": "SEQUENCE", 10762 + "type": "builtin", 10763 + "typeOf": 1, 10764 + "content": [ 10765 + { 10766 + "name": "Control", 10767 + "type": "defined" 10768 + } 10769 + ] 10770 + } 10771 + }, 10772 + "Control": { 10773 + "name": "Control", 10774 + "type": { 10775 + "name": "SEQUENCE", 10776 + "type": "builtin", 10777 + "content": [ 10778 + { 10779 + "id": "controlType", 10780 + "name": "LDAPOID", 10781 + "type": "defined" 10782 + }, 10783 + { 10784 + "id": "criticality", 10785 + "name": "BOOLEAN", 10786 + "type": "builtin", 10787 + "default": false 10788 + }, 10789 + { 10790 + "id": "controlValue", 10791 + "name": "OCTET STRING", 10792 + "type": "builtin", 10793 + "optional": true 10794 + } 10795 + ] 10796 + } 10797 + }, 10798 + "BindRequest": { 10799 + "name": "BindRequest", 10800 + "type": { 10801 + "name": "Application 0", 10802 + "type": "tag", 10803 + "class": "APPLICATION", 10804 + "explicit": false, 10805 + "content": [ 10806 + { 10807 + "name": "", 10808 + "type": { 10809 + "name": "SEQUENCE", 10810 + "type": "builtin", 10811 + "content": [ 10812 + { 10813 + "id": "version", 10814 + "name": "INTEGER", 10815 + "type": "builtin", 10816 + "range": [ 10817 + 1, 10818 + 127 10819 + ] 10820 + }, 10821 + { 10822 + "id": "name", 10823 + "name": "LDAPDN", 10824 + "type": "defined" 10825 + }, 10826 + { 10827 + "id": "authentication", 10828 + "name": "AuthenticationChoice", 10829 + "type": "defined" 10830 + } 10831 + ] 10832 + } 10833 + } 10834 + ] 10835 + } 10836 + }, 10837 + "AuthenticationChoice": { 10838 + "name": "AuthenticationChoice", 10839 + "type": { 10840 + "name": "CHOICE", 10841 + "type": "builtin", 10842 + "content": [ 10843 + { 10844 + "id": "simple", 10845 + "name": "[0]", 10846 + "type": "tag", 10847 + "class": "CONTEXT", 10848 + "explicit": false, 10849 + "content": [ 10850 + { 10851 + "name": "", 10852 + "type": { 10853 + "name": "OCTET STRING", 10854 + "type": "builtin" 10855 + } 10856 + } 10857 + ] 10858 + }, 10859 + { 10860 + "id": "sasl", 10861 + "name": "[3]", 10862 + "type": "tag", 10863 + "class": "CONTEXT", 10864 + "explicit": false, 10865 + "content": [ 10866 + { 10867 + "name": "", 10868 + "type": { 10869 + "name": "SaslCredentials", 10870 + "type": "defined" 10871 + } 10872 + } 10873 + ] 10874 + } 10875 + ] 10876 + } 10877 + }, 10878 + "SaslCredentials": { 10879 + "name": "SaslCredentials", 10880 + "type": { 10881 + "name": "SEQUENCE", 10882 + "type": "builtin", 10883 + "content": [ 10884 + { 10885 + "id": "mechanism", 10886 + "name": "LDAPString", 10887 + "type": "defined" 10888 + }, 10889 + { 10890 + "id": "credentials", 10891 + "name": "OCTET STRING", 10892 + "type": "builtin", 10893 + "optional": true 10894 + } 10895 + ] 10896 + } 10897 + }, 10898 + "BindResponse": { 10899 + "name": "BindResponse", 10900 + "type": { 10901 + "name": "ANY", 10902 + "type": "builtin" 10903 + } 10904 + }, 10905 + "UnbindRequest": { 10906 + "name": "UnbindRequest", 10907 + "type": { 10908 + "name": "Application 2", 10909 + "type": "tag", 10910 + "class": "APPLICATION", 10911 + "explicit": false, 10912 + "content": [ 10913 + { 10914 + "name": "", 10915 + "type": { 10916 + "name": "NULL", 10917 + "type": "builtin" 10918 + } 10919 + } 10920 + ] 10921 + } 10922 + }, 10923 + "SearchRequest": { 10924 + "name": "SearchRequest", 10925 + "type": { 10926 + "name": "Application 3", 10927 + "type": "tag", 10928 + "class": "APPLICATION", 10929 + "explicit": false, 10930 + "content": [ 10931 + { 10932 + "name": "", 10933 + "type": { 10934 + "name": "SEQUENCE", 10935 + "type": "builtin", 10936 + "content": [ 10937 + { 10938 + "id": "baseObject", 10939 + "name": "LDAPDN", 10940 + "type": "defined" 10941 + }, 10942 + { 10943 + "id": "scope", 10944 + "name": "ENUMERATED", 10945 + "type": "builtin", 10946 + "content": { 10947 + "baseObject": 0, 10948 + "singleLevel": 1, 10949 + "wholeSubtree": 2 10950 + } 10951 + }, 10952 + { 10953 + "id": "derefAliases", 10954 + "name": "ENUMERATED", 10955 + "type": "builtin", 10956 + "content": { 10957 + "neverDerefAliases": 0, 10958 + "derefInSearching": 1, 10959 + "derefFindingBaseObj": 2, 10960 + "derefAlways": 3 10961 + } 10962 + }, 10963 + { 10964 + "id": "sizeLimit", 10965 + "name": "INTEGER", 10966 + "type": "builtin", 10967 + "range": [ 10968 + 0, 10969 + "maxInt" 10970 + ] 10971 + }, 10972 + { 10973 + "id": "timeLimit", 10974 + "name": "INTEGER", 10975 + "type": "builtin", 10976 + "range": [ 10977 + 0, 10978 + "maxInt" 10979 + ] 10980 + }, 10981 + { 10982 + "id": "typesOnly", 10983 + "name": "BOOLEAN", 10984 + "type": "builtin" 10985 + }, 10986 + { 10987 + "id": "filter", 10988 + "name": "Filter", 10989 + "type": "defined" 10990 + }, 10991 + { 10992 + "id": "attributes", 10993 + "name": "AttributeSelection", 10994 + "type": "defined" 10995 + } 10996 + ] 10997 + } 10998 + } 10999 + ] 11000 + } 11001 + }, 11002 + "AttributeSelection": { 11003 + "name": "AttributeSelection", 11004 + "type": { 11005 + "name": "SEQUENCE", 11006 + "type": "builtin", 11007 + "typeOf": 1, 11008 + "content": [ 11009 + { 11010 + "name": "LDAPString", 11011 + "type": "defined" 11012 + } 11013 + ] 11014 + } 11015 + }, 11016 + "Filter": { 11017 + "name": "Filter", 11018 + "type": { 11019 + "name": "CHOICE", 11020 + "type": "builtin", 11021 + "content": [ 11022 + { 11023 + "id": "and", 11024 + "name": "[0]", 11025 + "type": "tag", 11026 + "class": "CONTEXT", 11027 + "explicit": false, 11028 + "content": [ 11029 + { 11030 + "name": "", 11031 + "type": { 11032 + "name": "SET", 11033 + "type": "builtin", 11034 + "typeOf": 1, 11035 + "size": [ 11036 + 1, 11037 + "MAX" 11038 + ], 11039 + "content": [ 11040 + { 11041 + "name": "Filter", 11042 + "type": "defined" 11043 + } 11044 + ] 11045 + } 11046 + } 11047 + ] 11048 + }, 11049 + { 11050 + "id": "or", 11051 + "name": "[1]", 11052 + "type": "tag", 11053 + "class": "CONTEXT", 11054 + "explicit": false, 11055 + "content": [ 11056 + { 11057 + "name": "", 11058 + "type": { 11059 + "name": "SET", 11060 + "type": "builtin", 11061 + "typeOf": 1, 11062 + "size": [ 11063 + 1, 11064 + "MAX" 11065 + ], 11066 + "content": [ 11067 + { 11068 + "name": "Filter", 11069 + "type": "defined" 11070 + } 11071 + ] 11072 + } 11073 + } 11074 + ] 11075 + }, 11076 + { 11077 + "id": "not", 11078 + "name": "[2]", 11079 + "type": "tag", 11080 + "class": "CONTEXT", 11081 + "explicit": false, 11082 + "content": [ 11083 + { 11084 + "name": "", 11085 + "type": { 11086 + "name": "Filter", 11087 + "type": "defined" 11088 + } 11089 + } 11090 + ] 11091 + }, 11092 + { 11093 + "id": "equalityMatch", 11094 + "name": "[3]", 11095 + "type": "tag", 11096 + "class": "CONTEXT", 11097 + "explicit": false, 11098 + "content": [ 11099 + { 11100 + "name": "", 11101 + "type": { 11102 + "name": "AttributeValueAssertion", 11103 + "type": "defined" 11104 + } 11105 + } 11106 + ] 11107 + }, 11108 + { 11109 + "id": "substrings", 11110 + "name": "[4]", 11111 + "type": "tag", 11112 + "class": "CONTEXT", 11113 + "explicit": false, 11114 + "content": [ 11115 + { 11116 + "name": "", 11117 + "type": { 11118 + "name": "SubstringFilter", 11119 + "type": "defined" 11120 + } 11121 + } 11122 + ] 11123 + }, 11124 + { 11125 + "id": "greaterOrEqual", 11126 + "name": "[5]", 11127 + "type": "tag", 11128 + "class": "CONTEXT", 11129 + "explicit": false, 11130 + "content": [ 11131 + { 11132 + "name": "", 11133 + "type": { 11134 + "name": "AttributeValueAssertion", 11135 + "type": "defined" 11136 + } 11137 + } 11138 + ] 11139 + }, 11140 + { 11141 + "id": "lessOrEqual", 11142 + "name": "[6]", 11143 + "type": "tag", 11144 + "class": "CONTEXT", 11145 + "explicit": false, 11146 + "content": [ 11147 + { 11148 + "name": "", 11149 + "type": { 11150 + "name": "AttributeValueAssertion", 11151 + "type": "defined" 11152 + } 11153 + } 11154 + ] 11155 + }, 11156 + { 11157 + "id": "present", 11158 + "name": "[7]", 11159 + "type": "tag", 11160 + "class": "CONTEXT", 11161 + "explicit": false, 11162 + "content": [ 11163 + { 11164 + "name": "", 11165 + "type": { 11166 + "name": "AttributeDescription", 11167 + "type": "defined" 11168 + } 11169 + } 11170 + ] 11171 + }, 11172 + { 11173 + "id": "approxMatch", 11174 + "name": "[8]", 11175 + "type": "tag", 11176 + "class": "CONTEXT", 11177 + "explicit": false, 11178 + "content": [ 11179 + { 11180 + "name": "", 11181 + "type": { 11182 + "name": "AttributeValueAssertion", 11183 + "type": "defined" 11184 + } 11185 + } 11186 + ] 11187 + }, 11188 + { 11189 + "id": "extensibleMatch", 11190 + "name": "[9]", 11191 + "type": "tag", 11192 + "class": "CONTEXT", 11193 + "explicit": false, 11194 + "content": [ 11195 + { 11196 + "name": "", 11197 + "type": { 11198 + "name": "MatchingRuleAssertion", 11199 + "type": "defined" 11200 + } 11201 + } 11202 + ] 11203 + } 11204 + ] 11205 + } 11206 + }, 11207 + "SubstringFilter": { 11208 + "name": "SubstringFilter", 11209 + "type": { 11210 + "name": "SEQUENCE", 11211 + "type": "builtin", 11212 + "content": [ 11213 + { 11214 + "id": "type", 11215 + "name": "AttributeDescription", 11216 + "type": "defined" 11217 + }, 11218 + { 11219 + "id": "substrings", 11220 + "name": "SEQUENCE", 11221 + "type": "builtin", 11222 + "typeOf": 1, 11223 + "size": [ 11224 + 1, 11225 + "MAX" 11226 + ], 11227 + "content": [ 11228 + { 11229 + "name": "CHOICE", 11230 + "type": "builtin", 11231 + "content": [ 11232 + { 11233 + "id": "initial", 11234 + "name": "[0]", 11235 + "type": "tag", 11236 + "class": "CONTEXT", 11237 + "explicit": false, 11238 + "content": [ 11239 + { 11240 + "name": "", 11241 + "type": { 11242 + "name": "AssertionValue", 11243 + "type": "defined" 11244 + } 11245 + } 11246 + ] 11247 + }, 11248 + { 11249 + "id": "any", 11250 + "name": "[1]", 11251 + "type": "tag", 11252 + "class": "CONTEXT", 11253 + "explicit": false, 11254 + "content": [ 11255 + { 11256 + "name": "", 11257 + "type": { 11258 + "name": "AssertionValue", 11259 + "type": "defined" 11260 + } 11261 + } 11262 + ] 11263 + }, 11264 + { 11265 + "id": "final", 11266 + "name": "[2]", 11267 + "type": "tag", 11268 + "class": "CONTEXT", 11269 + "explicit": false, 11270 + "content": [ 11271 + { 11272 + "name": "", 11273 + "type": { 11274 + "name": "AssertionValue", 11275 + "type": "defined" 11276 + } 11277 + } 11278 + ] 11279 + } 11280 + ] 11281 + } 11282 + ] 11283 + } 11284 + ] 11285 + } 11286 + }, 11287 + "MatchingRuleAssertion": { 11288 + "name": "MatchingRuleAssertion", 11289 + "type": { 11290 + "name": "SEQUENCE", 11291 + "type": "builtin", 11292 + "content": [ 11293 + { 11294 + "id": "matchingRule", 11295 + "name": "[1]", 11296 + "type": "tag", 11297 + "class": "CONTEXT", 11298 + "explicit": false, 11299 + "content": [ 11300 + { 11301 + "name": "", 11302 + "type": { 11303 + "name": "MatchingRuleId", 11304 + "type": "defined" 11305 + } 11306 + } 11307 + ], 11308 + "optional": true 11309 + }, 11310 + { 11311 + "id": "type", 11312 + "name": "[2]", 11313 + "type": "tag", 11314 + "class": "CONTEXT", 11315 + "explicit": false, 11316 + "content": [ 11317 + { 11318 + "name": "", 11319 + "type": { 11320 + "name": "AttributeDescription", 11321 + "type": "defined" 11322 + } 11323 + } 11324 + ], 11325 + "optional": true 11326 + }, 11327 + { 11328 + "id": "matchValue", 11329 + "name": "[3]", 11330 + "type": "tag", 11331 + "class": "CONTEXT", 11332 + "explicit": false, 11333 + "content": [ 11334 + { 11335 + "name": "", 11336 + "type": { 11337 + "name": "AssertionValue", 11338 + "type": "defined" 11339 + } 11340 + } 11341 + ] 11342 + }, 11343 + { 11344 + "id": "dnAttributes", 11345 + "name": "[4]", 11346 + "type": "tag", 11347 + "class": "CONTEXT", 11348 + "explicit": false, 11349 + "content": [ 11350 + { 11351 + "name": "", 11352 + "type": { 11353 + "name": "BOOLEAN", 11354 + "type": "builtin" 11355 + } 11356 + } 11357 + ], 11358 + "default": false 11359 + } 11360 + ] 11361 + } 11362 + }, 11363 + "SearchResultEntry": { 11364 + "name": "SearchResultEntry", 11365 + "type": { 11366 + "name": "Application 4", 11367 + "type": "tag", 11368 + "class": "APPLICATION", 11369 + "explicit": false, 11370 + "content": [ 11371 + { 11372 + "name": "", 11373 + "type": { 11374 + "name": "SEQUENCE", 11375 + "type": "builtin", 11376 + "content": [ 11377 + { 11378 + "id": "objectName", 11379 + "name": "LDAPDN", 11380 + "type": "defined" 11381 + }, 11382 + { 11383 + "id": "attributes", 11384 + "name": "PartialAttributeList", 11385 + "type": "defined" 11386 + } 11387 + ] 11388 + } 11389 + } 11390 + ] 11391 + } 11392 + }, 11393 + "PartialAttributeList": { 11394 + "name": "PartialAttributeList", 11395 + "type": { 11396 + "name": "SEQUENCE", 11397 + "type": "builtin", 11398 + "typeOf": 1, 11399 + "content": [ 11400 + { 11401 + "name": "PartialAttribute", 11402 + "type": "defined" 11403 + } 11404 + ] 11405 + } 11406 + }, 11407 + "SearchResultReference": { 11408 + "name": "SearchResultReference", 11409 + "type": { 11410 + "name": "Application 19", 11411 + "type": "tag", 11412 + "class": "APPLICATION", 11413 + "explicit": false, 11414 + "content": [ 11415 + { 11416 + "name": "", 11417 + "type": { 11418 + "name": "SEQUENCE", 11419 + "type": "builtin", 11420 + "typeOf": 1, 11421 + "size": [ 11422 + 1, 11423 + "MAX" 11424 + ], 11425 + "content": [ 11426 + { 11427 + "name": "URI", 11428 + "type": "defined" 11429 + } 11430 + ] 11431 + } 11432 + } 11433 + ] 11434 + } 11435 + }, 11436 + "SearchResultDone": { 11437 + "name": "SearchResultDone", 11438 + "type": { 11439 + "name": "Application 5", 11440 + "type": "tag", 11441 + "class": "APPLICATION", 11442 + "explicit": false, 11443 + "content": [ 11444 + { 11445 + "name": "", 11446 + "type": { 11447 + "name": "LDAPResult", 11448 + "type": "defined" 11449 + } 11450 + } 11451 + ] 11452 + } 11453 + }, 11454 + "ModifyRequest": { 11455 + "name": "ModifyRequest", 11456 + "type": { 11457 + "name": "Application 6", 11458 + "type": "tag", 11459 + "class": "APPLICATION", 11460 + "explicit": false, 11461 + "content": [ 11462 + { 11463 + "name": "", 11464 + "type": { 11465 + "name": "SEQUENCE", 11466 + "type": "builtin", 11467 + "content": [ 11468 + { 11469 + "id": "object", 11470 + "name": "LDAPDN", 11471 + "type": "defined" 11472 + }, 11473 + { 11474 + "id": "changes", 11475 + "name": "SEQUENCE", 11476 + "type": "builtin", 11477 + "typeOf": 1, 11478 + "content": [ 11479 + { 11480 + "name": "SEQUENCE", 11481 + "type": "builtin", 11482 + "content": [ 11483 + { 11484 + "id": "operation", 11485 + "name": "ENUMERATED", 11486 + "type": "builtin", 11487 + "content": { 11488 + "add": 0, 11489 + "delete": 1, 11490 + "replace": 2 11491 + } 11492 + }, 11493 + { 11494 + "id": "modification", 11495 + "name": "PartialAttribute", 11496 + "type": "defined" 11497 + } 11498 + ] 11499 + } 11500 + ] 11501 + } 11502 + ] 11503 + } 11504 + } 11505 + ] 11506 + } 11507 + }, 11508 + "ModifyResponse": { 11509 + "name": "ModifyResponse", 11510 + "type": { 11511 + "name": "Application 7", 11512 + "type": "tag", 11513 + "class": "APPLICATION", 11514 + "explicit": false, 11515 + "content": [ 11516 + { 11517 + "name": "", 11518 + "type": { 11519 + "name": "LDAPResult", 11520 + "type": "defined" 11521 + } 11522 + } 11523 + ] 11524 + } 11525 + }, 11526 + "AddRequest": { 11527 + "name": "AddRequest", 11528 + "type": { 11529 + "name": "Application 8", 11530 + "type": "tag", 11531 + "class": "APPLICATION", 11532 + "explicit": false, 11533 + "content": [ 11534 + { 11535 + "name": "", 11536 + "type": { 11537 + "name": "SEQUENCE", 11538 + "type": "builtin", 11539 + "content": [ 11540 + { 11541 + "id": "entry", 11542 + "name": "LDAPDN", 11543 + "type": "defined" 11544 + }, 11545 + { 11546 + "id": "attributes", 11547 + "name": "AttributeList", 11548 + "type": "defined" 11549 + } 11550 + ] 11551 + } 11552 + } 11553 + ] 11554 + } 11555 + }, 11556 + "AttributeList": { 11557 + "name": "AttributeList", 11558 + "type": { 11559 + "name": "SEQUENCE", 11560 + "type": "builtin", 11561 + "typeOf": 1, 11562 + "content": [ 11563 + { 11564 + "name": "Attribute", 11565 + "type": "defined" 11566 + } 11567 + ] 11568 + } 11569 + }, 11570 + "AddResponse": { 11571 + "name": "AddResponse", 11572 + "type": { 11573 + "name": "Application 9", 11574 + "type": "tag", 11575 + "class": "APPLICATION", 11576 + "explicit": false, 11577 + "content": [ 11578 + { 11579 + "name": "", 11580 + "type": { 11581 + "name": "LDAPResult", 11582 + "type": "defined" 11583 + } 11584 + } 11585 + ] 11586 + } 11587 + }, 11588 + "DelRequest": { 11589 + "name": "DelRequest", 11590 + "type": { 11591 + "name": "Application 10", 11592 + "type": "tag", 11593 + "class": "APPLICATION", 11594 + "explicit": false, 11595 + "content": [ 11596 + { 11597 + "name": "", 11598 + "type": { 11599 + "name": "LDAPDN", 11600 + "type": "defined" 11601 + } 11602 + } 11603 + ] 11604 + } 11605 + }, 11606 + "DelResponse": { 11607 + "name": "DelResponse", 11608 + "type": { 11609 + "name": "Application 11", 11610 + "type": "tag", 11611 + "class": "APPLICATION", 11612 + "explicit": false, 11613 + "content": [ 11614 + { 11615 + "name": "", 11616 + "type": { 11617 + "name": "LDAPResult", 11618 + "type": "defined" 11619 + } 11620 + } 11621 + ] 11622 + } 11623 + }, 11624 + "ModifyDNRequest": { 11625 + "name": "ModifyDNRequest", 11626 + "type": { 11627 + "name": "Application 12", 11628 + "type": "tag", 11629 + "class": "APPLICATION", 11630 + "explicit": false, 11631 + "content": [ 11632 + { 11633 + "name": "", 11634 + "type": { 11635 + "name": "SEQUENCE", 11636 + "type": "builtin", 11637 + "content": [ 11638 + { 11639 + "id": "entry", 11640 + "name": "LDAPDN", 11641 + "type": "defined" 11642 + }, 11643 + { 11644 + "id": "newrdn", 11645 + "name": "RelativeLDAPDN", 11646 + "type": "defined" 11647 + }, 11648 + { 11649 + "id": "deleteoldrdn", 11650 + "name": "BOOLEAN", 11651 + "type": "builtin" 11652 + }, 11653 + { 11654 + "id": "newSuperior", 11655 + "name": "[0]", 11656 + "type": "tag", 11657 + "class": "CONTEXT", 11658 + "explicit": false, 11659 + "content": [ 11660 + { 11661 + "name": "", 11662 + "type": { 11663 + "name": "LDAPDN", 11664 + "type": "defined" 11665 + } 11666 + } 11667 + ], 11668 + "optional": true 11669 + } 11670 + ] 11671 + } 11672 + } 11673 + ] 11674 + } 11675 + }, 11676 + "ModifyDNResponse": { 11677 + "name": "ModifyDNResponse", 11678 + "type": { 11679 + "name": "Application 13", 11680 + "type": "tag", 11681 + "class": "APPLICATION", 11682 + "explicit": false, 11683 + "content": [ 11684 + { 11685 + "name": "", 11686 + "type": { 11687 + "name": "LDAPResult", 11688 + "type": "defined" 11689 + } 11690 + } 11691 + ] 11692 + } 11693 + }, 11694 + "CompareRequest": { 11695 + "name": "CompareRequest", 11696 + "type": { 11697 + "name": "Application 14", 11698 + "type": "tag", 11699 + "class": "APPLICATION", 11700 + "explicit": false, 11701 + "content": [ 11702 + { 11703 + "name": "", 11704 + "type": { 11705 + "name": "SEQUENCE", 11706 + "type": "builtin", 11707 + "content": [ 11708 + { 11709 + "id": "entry", 11710 + "name": "LDAPDN", 11711 + "type": "defined" 11712 + }, 11713 + { 11714 + "id": "ava", 11715 + "name": "AttributeValueAssertion", 11716 + "type": "defined" 11717 + } 11718 + ] 11719 + } 11720 + } 11721 + ] 11722 + } 11723 + }, 11724 + "CompareResponse": { 11725 + "name": "CompareResponse", 11726 + "type": { 11727 + "name": "Application 15", 11728 + "type": "tag", 11729 + "class": "APPLICATION", 11730 + "explicit": false, 11731 + "content": [ 11732 + { 11733 + "name": "", 11734 + "type": { 11735 + "name": "LDAPResult", 11736 + "type": "defined" 11737 + } 11738 + } 11739 + ] 11740 + } 11741 + }, 11742 + "AbandonRequest": { 11743 + "name": "AbandonRequest", 11744 + "type": { 11745 + "name": "Application 16", 11746 + "type": "tag", 11747 + "class": "APPLICATION", 11748 + "explicit": false, 11749 + "content": [ 11750 + { 11751 + "name": "", 11752 + "type": { 11753 + "name": "MessageID", 11754 + "type": "defined" 11755 + } 11756 + } 11757 + ] 11758 + } 11759 + }, 11760 + "ExtendedRequest": { 11761 + "name": "ExtendedRequest", 11762 + "type": { 11763 + "name": "Application 23", 11764 + "type": "tag", 11765 + "class": "APPLICATION", 11766 + "explicit": false, 11767 + "content": [ 11768 + { 11769 + "name": "", 11770 + "type": { 11771 + "name": "SEQUENCE", 11772 + "type": "builtin", 11773 + "content": [ 11774 + { 11775 + "id": "requestName", 11776 + "name": "[0]", 11777 + "type": "tag", 11778 + "class": "CONTEXT", 11779 + "explicit": false, 11780 + "content": [ 11781 + { 11782 + "name": "", 11783 + "type": { 11784 + "name": "LDAPOID", 11785 + "type": "defined" 11786 + } 11787 + } 11788 + ] 11789 + }, 11790 + { 11791 + "id": "requestValue", 11792 + "name": "[1]", 11793 + "type": "tag", 11794 + "class": "CONTEXT", 11795 + "explicit": false, 11796 + "content": [ 11797 + { 11798 + "name": "", 11799 + "type": { 11800 + "name": "OCTET STRING", 11801 + "type": "builtin" 11802 + } 11803 + } 11804 + ], 11805 + "optional": true 11806 + } 11807 + ] 11808 + } 11809 + } 11810 + ] 11811 + } 11812 + }, 11813 + "ExtendedResponse": { 11814 + "name": "ExtendedResponse", 11815 + "type": { 11816 + "name": "ANY", 11817 + "type": "builtin" 11818 + } 11819 + }, 11820 + "IntermediateResponse": { 11821 + "name": "IntermediateResponse", 11822 + "type": { 11823 + "name": "Application 25", 11824 + "type": "tag", 11825 + "class": "APPLICATION", 11826 + "explicit": false, 11827 + "content": [ 11828 + { 11829 + "name": "", 11830 + "type": { 11831 + "name": "SEQUENCE", 11832 + "type": "builtin", 11833 + "content": [ 11834 + { 11835 + "id": "responseName", 11836 + "name": "[0]", 11837 + "type": "tag", 11838 + "class": "CONTEXT", 11839 + "explicit": false, 11840 + "content": [ 11841 + { 11842 + "name": "", 11843 + "type": { 11844 + "name": "LDAPOID", 11845 + "type": "defined" 11846 + } 11847 + } 11848 + ], 11849 + "optional": true 11850 + }, 11851 + { 11852 + "id": "responseValue", 11853 + "name": "[1]", 11854 + "type": "tag", 11855 + "class": "CONTEXT", 11856 + "explicit": false, 11857 + "content": [ 11858 + { 11859 + "name": "", 11860 + "type": { 11861 + "name": "OCTET STRING", 11862 + "type": "builtin" 11863 + } 11864 + } 11865 + ], 11866 + "optional": true 11867 + } 11868 + ] 11869 + } 11870 + } 11871 + ] 10372 11872 } 10373 11873 } 10374 11874 }
+4
tags.js
··· 1 1 export const tags = { 2 + "2.0.5": "2025-04-12", 3 + "2.0.4": "2024-05-08", 4 + "2.0.3": "2024-05-06", 5 + "2.0.2": "2024-04-20", 2 6 "2.0.1": "2024-03-28", 3 7 "2.0.0": "2024-03-26", 4 8 "1.3.0": "2024-03-26",
+132 -38
test.js
··· 1 1 #!/usr/bin/env node 2 2 3 - import { ASN1 } from './asn1.js'; 3 + import { ASN1, Stream } from './asn1.js'; 4 4 import { Hex } from './hex.js'; 5 + import { Base64 } from './base64.js'; 6 + import { Int10 } from './int10.js'; 7 + 8 + const all = (process.argv[2] == 'all'); 5 9 6 - const 7 - all = (process.argv[2] == 'all'); 10 + /** @type {Array<Tests>} */ 11 + const tests = []; 12 + 13 + const stats = { 14 + run: 0, 15 + error: 0, 16 + }; 17 + 18 + /** 19 + * A class for managing and executing tests. 20 + */ 21 + class Tests { 22 + /** 23 + * An array to store test data. 24 + * @type {Array<unknown>} 25 + */ 26 + data; 27 + 28 + /** 29 + * Checks a row of test data. 30 + * @param {Function} t - How to test a row of data. 31 + */ 32 + checkRow; 33 + 34 + /** 35 + * Constructs a new Tests instance. 36 + * @param {Function} checkRow - A function to check each row of data. 37 + * @param {Array<unknown>} data - The test data to be processed. 38 + */ 39 + constructor(checkRow, data) { 40 + this.checkRow = checkRow; 41 + this.data = data; 42 + } 43 + 44 + /** 45 + * Executes the tests and checks their results for all rows. 46 + */ 47 + checkAll() { 48 + for (const t of this.data) 49 + this.checkRow(t); 50 + } 8 51 9 - const tests = [ 52 + /** 53 + * Prints the result of a test, indicating if it passed or failed. 54 + * @param {unknown} result The actual result of the test. 55 + * @param {unknown} expected The expected result of the test. 56 + * @param {string} comment A comment describing the test. 57 + */ 58 + checkResult(result, expected, comment) { 59 + ++stats.run; 60 + if (!result || result == expected) { 61 + if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 62 + } else { 63 + ++stats.error; 64 + console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 65 + console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 66 + console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 67 + } 68 + } 69 + } 70 + 71 + tests.push(new Tests(function (t) { 72 + const input = t[0], 73 + expected = t[1], 74 + comment = t[2]; 75 + let result; 76 + try { 77 + let node = ASN1.decode(Hex.decode(input)); 78 + if (typeof expected == 'function') 79 + result = expected(node); 80 + else 81 + result = node.content(); 82 + //TODO: check structure, not only first level content 83 + } catch (e) { 84 + result = 'Exception:\n' + e; 85 + } 86 + if (expected instanceof RegExp) 87 + result = expected.test(result) ? null : 'does not match'; 88 + this.checkResult(result, expected, comment); 89 + }, [ 10 90 // RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html 11 91 ['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'], 12 92 ['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'], ··· 88 168 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 89 169 ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 90 170 ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 91 - ]; 171 + ])); 92 172 93 - let 94 - run = 0, 95 - expErr = 0, 96 - error = 0; 97 - tests.forEach(function (t) { 98 - const input = t[0], 99 - expected = t[1], 100 - comment = t[2]; 101 - let result; 102 - try { 103 - let node = ASN1.decode(Hex.decode(input)); 104 - if (typeof expected == 'function') 105 - result = expected(node); 106 - else 107 - result = node.content(); 108 - //TODO: check structure, not only first level content 109 - } catch (e) { 110 - result = 'Exception:\n' + e; 111 - } 112 - if (expected instanceof RegExp) 113 - result = expected.test(result) ? null : 'does not match'; 114 - ++run; 115 - if (!result || result == expected) { 116 - if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 117 - } else { 118 - ++error; 119 - console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 120 - console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 121 - console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 122 - } 123 - }); 124 - console.log(run + ' tested, ' + expErr + ' expected, ' + error + ' errors.'); 125 - process.exit(error ? 1 : 0); 173 + tests.push(new Tests(function (t) { 174 + let bin = Base64.decode(t); 175 + let url = new Stream(bin, 0).b64Dump(0, bin.length); 176 + // check base64url encoding 177 + this.checkResult(url, t.replace(/\n/g, '').replace(/=*$/g, ''), 'Base64url: ' + bin.length + ' bytes'); 178 + // check conversion from base64url to base64 179 + let pretty = Base64.pretty(url); 180 + this.checkResult(pretty, t, 'Base64pretty: ' + bin.length + ' bytes'); 181 + let std = new Stream(bin, 0).b64Dump(0, bin.length, 'std'); 182 + // check direct base64 encoding 183 + this.checkResult(std, t.replace(/\n/g, ''), 'Base64: ' + bin.length + ' bytes'); 184 + }, [ 185 + 'AA==', 186 + 'ABA=', 187 + 'ABCD', 188 + 'ABCDEA==', 189 + 'ABCDEFE=', 190 + 'ABCDEFGH', 191 + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR\nSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456w==', 192 + ])); 193 + 194 + tests.push(new Tests(function (t) { 195 + this.row = (0|this.row) + 1; 196 + this.num = this.num || new Int10(); 197 + this.num.mulAdd(t[0], t[1]); 198 + this.checkResult(this.num.toString(), t[2], 'Int10 row ' + this.row); 199 + }, [ 200 + [0, 1000000000, '1000000000'], 201 + [256, 23, '256000000023'], 202 + [256, 23, '65536000005911'], 203 + [256, 23, '16777216001513239'], 204 + [256, 23, '4294967296387389207'], 205 + [256, 23, '1099511627875171637015'], 206 + [256, 23, '281474976736043939075863'], 207 + [253, 1, '71213169114219116586193340'], 208 + [253, 1, '18016931785897436496306915021'], 209 + [253, 1, '4558283741832051433565649500314'], 210 + [253, 1, '1153245786683509012692109323579443'], 211 + [253, 1, '291771184030927780211103658865599080'], 212 + [1, 0, '291771184030927780211103658865599080'], 213 + ])); 214 + 215 + for (const t of tests) 216 + t.checkAll(); 217 + 218 + console.log(stats.run + ' tested, ' + stats.error + ' errors.'); 219 + process.exit(stats.error ? 1 : 0);
+37
theme.js
··· 1 + // set dark theme depending on OS settings 2 + function setTheme(theme) { 3 + if (theme == 'os') { 4 + let prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); 5 + if (prefersDarkScheme.matches) { 6 + theme = 'dark'; 7 + } else { 8 + theme = 'light'; 9 + } 10 + } 11 + document.documentElement.style['color-scheme'] = theme; 12 + document.querySelector('html').setAttribute('data-theme', theme); 13 + // set the theme-color for iOS devices 14 + let bgColor = getComputedStyle(document.documentElement).getPropertyValue('--main-bg-color'); 15 + let metaThemeColor = document.querySelector('meta[name=theme-color]'); 16 + metaThemeColor.setAttribute('content', bgColor); 17 + } 18 + // activate selected theme 19 + let theme = 'os'; 20 + const localStorageTheme = localStorage.getItem('theme'); 21 + if (localStorageTheme) { 22 + theme = localStorageTheme; 23 + } 24 + setTheme(theme); 25 + // add handler to theme selection element 26 + const selectTheme = document.getElementById('theme-select'); 27 + if (selectTheme) { 28 + selectTheme.addEventListener ('change', function () { 29 + localStorage.setItem('theme', selectTheme.value); 30 + setTheme(selectTheme.value); 31 + }); 32 + if (theme == 'light') { 33 + selectTheme.selectedIndex = 2; 34 + } else if (theme == 'dark') { 35 + selectTheme.selectedIndex = 1; 36 + } 37 + }
+1
tree-icon-dark.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="28" height="14"><circle cx="6.903" cy="7.102" r="5.165" style="fill:#000;fill-opacity:1;stroke:#555;stroke-width:1.03676;stroke-dasharray:none;stroke-opacity:1"/><circle cx="21.133" cy="7.029" r="5.165" style="fill:#000;fill-opacity:1;stroke:#555;stroke-width:1.03676;stroke-dasharray:none;stroke-opacity:1"/><path d="M17.908 7.071h6.783" style="opacity:1;fill:none;fill-opacity:1;stroke:#555;stroke-width:1.03676;stroke-dasharray:none;stroke-opacity:1"/><g style="fill:none;fill-opacity:1;stroke:#555;stroke-width:.518375;stroke-dasharray:none;stroke-opacity:1"><path d="M5.231 9.992h9.466" style="opacity:.992268;fill:none;fill-opacity:1;stroke:#555;stroke-width:1.44663;stroke-dasharray:none;stroke-opacity:1" transform="translate(-.289 -.083)scale(.71667)"/><path d="M10.006 5.242v9.465" style="opacity:1;fill:none;fill-opacity:1;stroke:#555;stroke-width:1.44663;stroke-dasharray:none;stroke-opacity:1" transform="translate(-.289 -.083)scale(.71667)"/></g></svg>
+1
tree-icon-light.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="28" height="14"><circle cx="6.936" cy="7.247" r="5.165" style="fill:silver;fill-opacity:1;stroke:#999;stroke-width:1.03676;stroke-dasharray:none;stroke-opacity:1"/><circle cx="21.166" cy="7.174" r="5.165" style="fill:silver;fill-opacity:1;stroke:#999;stroke-width:1.03676;stroke-dasharray:none;stroke-opacity:1"/><path d="M17.94 7.216h6.784" style="fill:none;fill-opacity:1;stroke:#999;stroke-width:1.03676;stroke-dasharray:none;stroke-opacity:1"/><g style="fill:none;fill-opacity:1;stroke:#999;stroke-width:.518375;stroke-dasharray:none;stroke-opacity:1"><path d="M5.231 9.992h9.466" style="opacity:.992268;fill:none;fill-opacity:1;stroke:#999;stroke-width:1.44663;stroke-dasharray:none;stroke-opacity:1" transform="translate(-.256 .062)scale(.71667)"/><path d="M10.006 5.242v9.465" style="opacity:1;fill:none;fill-opacity:1;stroke:#999;stroke-width:1.44663;stroke-dasharray:none;stroke-opacity:1" transform="translate(-.256 .062)scale(.71667)"/></g></svg>
+1 -1
updateRFC.sh
··· 1 1 #/bin/sh 2 - RFCs="5280 5208 3369 3161 2986 4211 4210 8017" 2 + RFCs="5280 5208 3369 3161 2986 4211 4210 8017 4511" 3 3 downloadRFC() { 4 4 URL="https://www.ietf.org/rfc/rfc$1.txt" 5 5 if [ -x /usr/bin/fetch ]; then
+37
vite.config.js
··· 1 + import fs from 'node:fs'; 2 + import { defineConfig } from 'vite'; 3 + import { viteSingleFile } from 'vite-plugin-singlefile'; 4 + import pluginDom from 'vite-plugin-dom'; 5 + import { DomUtils } from 'htmlparser2'; 6 + 7 + const removeNodes = [ 'rowExamples' ]; 8 + 9 + const preventSVGEmit = () => { 10 + return { 11 + generateBundle(opts, bundle) { 12 + for (const key in bundle) 13 + if (key.endsWith('.svg')) 14 + delete bundle[key]; 15 + }, 16 + }; 17 + }; 18 + 19 + export default defineConfig({ 20 + plugins: [ 21 + preventSVGEmit(), 22 + pluginDom({ 23 + applyOnMode: true, // all modes 24 + handler: node => { 25 + if (removeNodes.includes(node.attribs.id)) 26 + DomUtils.removeElement(node); 27 + else if (node.name == 'link' && node.attribs.rel == 'icon') 28 + node.attribs.href = 'data:image/svg+xml;base64,' + btoa(fs.readFileSync('favicon.svg', 'ascii').replace(/^([^<]+|<[^s]|<s[^v]|<sv[^g])+/, '').trim()); 29 + }, 30 + }), 31 + viteSingleFile(), 32 + ], 33 + build: { 34 + minify: false, 35 + cssMinify: false, 36 + }, 37 + });