Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tree view #87

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c0b4a11
Merge pull request #2 from lapo-luchini/trunk
olibu Oct 7, 2023
64546e4
hex copy on click
olibu Jan 28, 2024
151289b
Context menu and copyAsString added
olibu Jan 28, 2024
5efe7f7
Context menu initially hidden
Jan 29, 2024
36dd239
Fix: Copy the string to clipboard
Jan 29, 2024
01b1525
New: Copy as pretty
Jan 29, 2024
4978b90
New: Align context menu to left
Jan 29, 2024
c7cc3ba
Negative list for set and sequence
olibu Jan 29, 2024
c69ed8d
Fix: Show context menu at correct position
olibu Jan 29, 2024
0b9b3f3
Simple marker with toggle method
olibu Jan 29, 2024
2256505
Merge branch 'feature/hexcopy' into release
olibu Jan 31, 2024
8eb7457
Indention fixed
olibu Jan 31, 2024
170ef08
Merge branch 'feature/hexcopy' into release
olibu Jan 31, 2024
620bc08
Merge remote-tracking branch 'lapo/trunk' into release
olibu Mar 30, 2024
80e7a8b
Menu added to tree view
olibu Mar 31, 2024
6dcb057
Pretty text from innerText
olibu Mar 31, 2024
88d7afd
new: tree view
olibu Apr 10, 2024
59d44dc
Only highlight the current selected node
olibu Apr 19, 2024
1dc1323
smaller padding as of tree layout
olibu Apr 19, 2024
60383f9
Smaller borders and icon
olibu Apr 19, 2024
a6cf850
do not show the menu in case of clicking the icon
olibu Apr 19, 2024
174df2c
Installation of node to execute lint correctly
olibu Apr 19, 2024
48fbe0b
Singlequotes
olibu Apr 19, 2024
b4ac6bd
Merge branch 'feature/treeview' into release
olibu Apr 19, 2024
fbd2b26
Only show context menu on highlighted element
olibu Apr 20, 2024
ea3c28f
Less offensive tree icons
olibu Apr 20, 2024
1f9913c
Original layout recovered
olibu Apr 20, 2024
0d84f33
Layout issue fixed
olibu Apr 20, 2024
ba21d40
Fix: Zoom issues of the border line in treeview
olibu Apr 22, 2024
d303e65
Indentation fixed
olibu Apr 23, 2024
7ecec55
Zoom fix via css media query instead of JS
olibu May 12, 2024
1b1b3ac
Resolving merge conflict
olibu May 12, 2024
678a19a
Push to trigger failed build again
olibu May 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ jobs:
node-version: [ 12.20.0, latest ]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test all
- run: npm run testdefs
if: matrix.node-version == 'latest'
- run: npm run lint
if: matrix.node-version == 'latest'
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ _MTN/
.DS_Store/
.vscode/
node_modules/
dist/
package-lock.json
pnpm-lock.yaml
# Artifacts from release.sh
index-local.html
sha256sums.asc
asn1js.zip
# Artifacts from mirror_to_github.sh
Expand Down
2 changes: 0 additions & 2 deletions .mtn-ignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
[.]DS_Store$
[.]vscode$
node_modules$
dist$
package-lock[.]json
pnpm-lock[.]yaml
# Artifacts from release.sh
index-local.html
sha256sums[.]asc
asn1js[.]zip
# Artifacts from mirror_to_github.sh
Expand Down
34 changes: 7 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ asn1js is a JavaScript generic ASN.1 parser/decoder that can decode any valid AS

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).

Usage with `nodejs`
-------------------
Usage with `npm` / `yarn`
-------------------------

This package can be installed with either npm or yarn via the following commands:

Expand All @@ -17,38 +17,19 @@ pnpm install @lapo/asn1js
yarn add @lapo/asn1js
```

You can import the classes like this:
Assuming a standard javascript bundler is setup you can import it like so:

```js
import { ASN1 } from '@lapo/asn1js';
import ASN1 from '@lapo/asn1js';
```

A submodule of this package can also be imported:

```js
import { Hex } from '@lapo/asn1js/hex.js';
import Hex from '@lapo/asn1js/hex';
```

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`):

```js
const
{ ASN1 } = require('@lapo/asn1js'),
{ Hex } = require('@lapo/asn1js/hex.js');
console.log(ASN1.decode(Hex.decode('06032B6570')).content());
```

On older NodeJS you instead need to use async `import`:

```js
async function main() {
const
{ ASN1 } = await import('@lapo/asn1js'),
{ Hex } = await import('@lapo/asn1js/hex.js');
console.log(ASN1.decode(Hex.decode('06032B6570')).content());
}
main();
```
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.

Usage on the web
--------------------
Expand All @@ -57,7 +38,7 @@ Can be [tested on JSFiddle](https://jsfiddle.net/lapo/y6t2wo7q/).

```html
<script>
import { ASN1 } from 'https://unpkg.com/@lapo/[email protected]/asn1.js';
import { ASN1} from 'https://unpkg.com/@lapo/[email protected]/asn1.js';
import { Hex } from 'https://unpkg.com/@lapo/[email protected]/hex.js';

document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
Expand Down Expand Up @@ -89,7 +70,6 @@ links

- [official website](https://lapo.it/asn1js/)
- [dedicated domain](https://asn1js.eu/)
- [single-file version working locally](https://asn1js.eu/index-local.html) (just save this link)
- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
- [Ohloh code stats](https://www.openhub.net/p/asn1js)
20 changes: 12 additions & 8 deletions context.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ export function bindContextMenu(node) {
const type = node.asn1.typeName();
const valueEnabled = type != 'SET' && type != 'SEQUENCE';
node.onclick = function (event) {
contextMenu.style.left = event.pageX + 'px';
contextMenu.style.top = event.pageY + 'px';
contextMenu.style.visibility = 'visible';
contextMenu.node = this;
btnHideTree.innerText = (node.className == 'node') ? 'Hide subtree' : 'Show subtree';
btnHideTree.style.display = node.className.startsWith('node') ? 'block' : 'none';
btnCopyValue.style.display = valueEnabled ? 'block' : 'none';
event.stopPropagation();
// do not show the menu in case of clicking the icon
if (event.srcElement.nodeName === 'SPAN') {
contextMenu.style.left = event.pageX + 'px';
contextMenu.style.top = event.pageY + 'px';
contextMenu.style.visibility = 'visible';
contextMenu.node = this;
btnHideTree.innerText = (node.className == 'node') ? 'Hide subtree' : 'Show subtree';
btnHideTree.style.display = node.className.startsWith('node') ? 'block' : 'none';
btnCopyValue.style.display = valueEnabled ? 'block' : 'none';
event.preventDefault();
event.stopPropagation();
}
};
}

Expand Down
16 changes: 4 additions & 12 deletions defs.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class Defs {
// return r.types[name];
return Defs.moduleAndType(mod, name);
}
throw new Error('Type not found: ' + name);
throw 'Type not found: ' + name;
}

static match(value, def, stats = { total: 0, recognized: 0, defs: {} }) {
Expand All @@ -90,20 +90,12 @@ export class Defs {
type = def.content[0];
else {
let tn = subval.typeName().replaceAll('_', ' ');
for (;;) {
do {
type = def.content[j++];
if (!type || typeof type != 'object') break;
// type = translate(type, tn);
if (type?.type?.type)
type = type.type;
if (type.type == 'defined') {
let t2 = translate(type, tn);
if (t2.type.name == tn) break; // exact match
if (t2.type.name == 'ANY') break; // good enough
}
if (type.name == tn) break; // exact match
if (type.name == 'ANY') break; // good enough
if (!('optional' in type || 'default' in type)) break;
}
} while (type && typeof type == 'object' && ('optional' in type || 'default' in type) && type.name != 'ANY' && type.name != tn);
if (type?.type == 'builtin' || type?.type == 'defined') {
let v = subval.content();
if (typeof v == 'string')
Expand Down
50 changes: 41 additions & 9 deletions dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,17 @@ const

export class ASN1DOM extends ASN1 {

buf2hex(buffer) {
return [...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, '0')).join(' ');
}

toDOM(spaces) {
spaces = spaces || '';
let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
let node = DOM.tag('div', 'node');
node.asn1 = this;
let node;
node = document.createElement('li');
node.asn1 = this;
let head = DOM.tag('span', 'head');
head.appendChild(DOM.tag('span', 'spaces', spaces));
const typeName = this.typeName().replace(/_/g, ' ');
if (this.def) {
if (this.def.id) {
Expand All @@ -75,6 +79,8 @@ export class ASN1DOM extends ASN1 {
head.appendChild(DOM.space());
}
}
head.setAttribute('pos', this.posStart());
head.setAttribute('end', this.posEnd());
head.appendChild(DOM.text(typeName));
let content;
try {
Expand Down Expand Up @@ -111,8 +117,28 @@ export class ASN1DOM extends ASN1 {
content = content.replace(/</g, '&lt;');
content = content.replace(/\n/g, '<br>');
}
node.appendChild(head);
this.node = node;
// add the li and details section for this node
let contentNode;
let childNode;
if (this.sub !== null) {
let details = document.createElement('details');
details.setAttribute('open', '');
node.appendChild(details);
let summary = document.createElement('summary');
summary.setAttribute('class', 'node');
details.appendChild(summary);
summary.appendChild(head);
// summary.setAttribute('class', 'node');
contentNode = summary;
childNode = details;
}
else {
contentNode = node;
contentNode.setAttribute('class', 'node');
contentNode.appendChild(head);
}

this.node = contentNode;
this.head = head;
let value = DOM.tag('div', 'value');
let s = 'Offset: ' + this.stream.pos + '<br>';
Expand All @@ -135,14 +161,16 @@ export class ASN1DOM extends ASN1 {
}
}
value.innerHTML = s;
node.appendChild(value);
contentNode.appendChild(value);
let sub = DOM.tag('div', 'sub');
if (this.sub !== null) {
let ul = document.createElement('ul');
childNode.appendChild(ul);

spaces += '\xA0 ';
for (let i = 0, max = this.sub.length; i < max; ++i)
sub.appendChild(this.sub[i].toDOM(spaces));
ul.appendChild(this.sub[i].toDOM(spaces));
}
node.appendChild(sub);
bindContextMenu(node);
return node;
}
Expand All @@ -169,13 +197,14 @@ export class ASN1DOM extends ASN1 {
this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
node.asn1 = this;
node.onmouseover = function () {
node.onmouseover = function (event) {
let current = !root.selected;
if (current) {
root.selected = this.asn1;
this.className = 'hexCurrent';
}
this.asn1.fakeHover(current);
event.stopPropagation();
};
node.onmouseout = function () {
let current = (root.selected == this.asn1);
Expand All @@ -199,6 +228,9 @@ export class ASN1DOM extends ASN1 {
node.appendChild(skip);
}
}
// set the current start and end position as an attribute at the node to know the selected area
node.setAttribute('pos', this.posStart());
node.setAttribute('end', this.posEnd());
this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen());
this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent());
if (this.sub === null) {
Expand Down
45 changes: 18 additions & 27 deletions dumpASN1.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { Defs } from './defs.js';
const
colYellow = '\x1b[33m',
colBlue = '\x1b[34m',
colReset = '\x1b[0m',
reDataURI = /^data:(?:[a-z-]+[/][a-z.+-]+;)?base64,([A-Za-z0-9+/=\s]+)$/;
colReset = '\x1b[0m';

function print(value, indent) {
if (indent === undefined) indent = '';
Expand Down Expand Up @@ -41,38 +40,30 @@ function print(value, indent) {
return s;
}

const filename = process.argv[2];
const match = reDataURI.exec(filename);
let content = match
? Buffer.from(match[1])
: fs.readFileSync(filename);
let content = fs.readFileSync(process.argv[2]);
try { // try PEM first
content = Base64.unarmor(content);
} catch (e) { // try DER/BER then
}
let result = ASN1.decode(content);
content = null;
const t0 = performance.now();
if (process.argv.length == 5) {
Defs.match(result, Defs.moduleAndType(Defs.RFC[process.argv[3]], process.argv[4]));
} else {
const types = Defs.commonTypes
.map(type => {
const stats = Defs.match(result, type);
return { type, match: stats.recognized / stats.total };
})
.sort((a, b) => b.match - a.match);
const t1 = performance.now();
console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:');
for (const t of types)
console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description);
Defs.match(result, types[0].type);
// const stats = Defs.match(result, types[0].type);
// console.log('Stats:', stats);
console.log('Parsed as:', result.def);
// const type = searchType(process.argv[2]);
// const stats = applyDef(result, type);
}
const types = Defs.commonTypes
.map(type => {
const stats = Defs.match(result, type);
return { type, match: stats.recognized / stats.total };
})
.sort((a, b) => b.match - a.match);
const t1 = performance.now();
console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:');
for (const t of types)
console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description);
Defs.match(result, types[0].type);
// const stats = Defs.match(result, types[0].type);
// console.log('Stats:', stats);
console.log('Parsed as:', result.def);
// const type = searchType(process.argv[2]);
// const stats = applyDef(result, type);
console.log(print(result));
// console.log('Stats:', (stats.recognized * 100 / stats.total).toFixed(2) + '%');
// // print(result, searchType(process.argv[2]), stats);
Expand Down
9 changes: 0 additions & 9 deletions examples/cms-password.p7m

This file was deleted.

23 changes: 23 additions & 0 deletions index-dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,26 @@ h1 {
#dump .hexCurrent .hex { background-color: #474747; }
#dump .hexCurrent .tag { color: #6db0fc; }
#dump .hexCurrent .dlen { color: #00b6b6; }

.treecollapse ul li{
border-left: 1px solid #333;
left: 0.5px;
}

.treecollapse ul li::before{
content: '';
display: block;
position: absolute;
top: calc(var(--spacing) / -1.6);
left: var(--zoom-fix);
width: calc(var(--spacing) + 2px);
height: calc(var(--spacing) + 1px);
border: solid #333;
border-width: 0 0 1px 1px;
}

.treecollapse summary::before{
z-index: 1;
top: 1px;
background: url('tree-icon-dark.svg');
}
Loading
Loading