1// ASN.1 JavaScript decoder
2// Copyright (c) 2008-2023 Lapo Luchini <lapo@lapo.it>
3
4// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16(typeof define != 'undefined' ? define : function (factory) { 'use strict';
17 if (typeof module == 'object') module.exports = factory(function (name) { return require(name); });
18 else window.dom = factory(function (name) { return window[name.substring(2)]; });
19})(function (require) {
20'use strict';
21
22const
23 ASN1 = require('./asn1'),
24 oids = require('./oids'),
25 lineLength = 80,
26 contentLength = 8 * lineLength,
27 DOM = {
28 ellipsis: '\u2026',
29 tag: function (tagName, className, text) {
30 let t = document.createElement(tagName);
31 t.className = className;
32 if (text) t.innerText = text;
33 return t;
34 },
35 text: function (str) {
36 return document.createTextNode(str);
37 },
38 space: function () {
39 return DOM.tag('span', 'spaces', ' ');
40 },
41 breakLines: function (str, length) {
42 let lines = str.split(/\r?\n/),
43 o = '';
44 for (let i = 0; i < lines.length; ++i) {
45 let line = lines[i];
46 if (i > 0) o += '\n';
47 while (line.length > length) {
48 o += line.substring(0, length);
49 o += '\n';
50 line = line.substring(length);
51 }
52 o += line;
53 }
54 return o;
55 }
56 }
57
58class ASN1DOM extends ASN1 {
59
60 toDOM(spaces) {
61 spaces = spaces || '';
62 let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
63 let node = DOM.tag('div', 'node');
64 node.asn1 = this;
65 let head = DOM.tag('div', 'head');
66 head.appendChild(DOM.tag('span', 'spaces', spaces));
67 const typeName = this.typeName().replace(/_/g, ' ');
68 if (this.def) {
69 if (this.def.id) {
70 head.appendChild(DOM.tag('span', 'name id', this.def.id));
71 head.appendChild(DOM.space());
72 }
73 if (this.def.name && this.def.name != typeName) {
74 head.appendChild(DOM.tag('span', 'name type', this.def.name));
75 head.appendChild(DOM.space());
76 }
77 if (this.def.mismatch) {
78 head.appendChild(DOM.tag('span', 'name type', '[?]'));
79 head.appendChild(DOM.space());
80 }
81 }
82 head.appendChild(DOM.text(typeName));
83 let content = this.content(contentLength);
84 let oid;
85 if (content !== null) {
86 let preview = DOM.tag('span', 'preview'),
87 shortContent;
88 if (isOID)
89 content = content.split('\n', 1)[0];
90 shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content;
91 preview.appendChild(DOM.space());
92 preview.appendChild(DOM.text(shortContent));
93 if (isOID) {
94 oid = oids[content];
95 if (oid) {
96 if (oid.d) {
97 preview.appendChild(DOM.space());
98 let oidd = DOM.tag('span', 'oid description', oid.d);
99 preview.appendChild(oidd);
100 }
101 if (oid.c) {
102 preview.appendChild(DOM.space());
103 let oidc = DOM.tag('span', 'oid comment', '(' + oid.c + ')');
104 preview.appendChild(oidc);
105 }
106 }
107 }
108 head.appendChild(preview);
109 content = DOM.breakLines(content, lineLength);
110 content = content.replace(/</g, '<');
111 content = content.replace(/\n/g, '<br>');
112 }
113 node.appendChild(head);
114 this.node = node;
115 this.head = head;
116 let value = DOM.tag('div', 'value');
117 let s = 'Offset: ' + this.stream.pos + '<br>';
118 s += 'Length: ' + this.header + '+';
119 if (this.length >= 0)
120 s += this.length;
121 else
122 s += (-this.length) + ' (undefined)';
123 if (this.tag.tagConstructed)
124 s += '<br>(constructed)';
125 else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
126 s += '<br>(encapsulates)';
127 //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: "
128 if (content !== null) {
129 s += '<br>Value:<br><b>' + content + '</b>';
130 if (isOID && oid) {
131 if (oid.d) s += '<br>' + oid.d;
132 if (oid.c) s += '<br>' + oid.c;
133 if (oid.w) s += '<br>(warning!)';
134 }
135 }
136 value.innerHTML = s;
137 node.appendChild(value);
138 let sub = DOM.tag('div', 'sub');
139 if (this.sub !== null) {
140 spaces += '\xA0 ';
141 for (let i = 0, max = this.sub.length; i < max; ++i)
142 sub.appendChild(this.sub[i].toDOM(spaces));
143 }
144 node.appendChild(sub);
145 head.onclick = function () {
146 node.className = (node.className == 'node collapsed') ? 'node' : 'node collapsed';
147 };
148 return node;
149 }
150 fakeHover(current) {
151 this.node.className += ' hover';
152 if (current)
153 this.head.className += ' hover';
154 }
155 fakeOut(current) {
156 let re = / ?hover/;
157 this.node.className = this.node.className.replace(re, '');
158 if (current)
159 this.head.className = this.head.className.replace(re, '');
160 }
161 toHexDOM_sub(node, className, stream, start, end) {
162 if (start >= end)
163 return;
164 let sub = DOM.tag('span', className, stream.hexDump(start, end));
165 node.appendChild(sub);
166 }
167 toHexDOM(root, trim=true) {
168 let node = DOM.tag('span', 'hex');
169 if (root === undefined) root = node;
170 this.head.hexNode = node;
171 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
172 this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
173 node.asn1 = this;
174 node.onmouseover = function () {
175 let current = !root.selected;
176 if (current) {
177 root.selected = this.asn1;
178 this.className = 'hexCurrent';
179 }
180 this.asn1.fakeHover(current);
181 };
182 node.onmouseout = function () {
183 let current = (root.selected == this.asn1);
184 this.asn1.fakeOut(current);
185 if (current) {
186 root.selected = null;
187 this.className = 'hex';
188 }
189 };
190 if (root == node) {
191 let lineStart = this.posStart() & 0xF;
192 if (lineStart != 0) {
193 let skip = DOM.tag('span', 'skip');
194 let skipStr = '';
195 for (let j = lineStart; j > 0; --j)
196 skipStr += ' ';
197 if (lineStart >= 8)
198 skipStr += ' ';
199 skip.innerText = skipStr;
200 node.appendChild(skip);
201 }
202 }
203 this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen());
204 this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent());
205 if (this.sub === null) {
206 let start = this.posContent();
207 let end = this.posEnd();
208 if (!trim || end - start < 10 * 16)
209 node.appendChild(DOM.text(
210 this.stream.hexDump(start, end)));
211 else {
212 let end1 = start + 5 * 16 - (start & 0xF);
213 let start2 = end - 16 - (end & 0xF);
214 node.appendChild(DOM.text(this.stream.hexDump(start, end1)));
215 node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n'));
216 node.appendChild(DOM.text(this.stream.hexDump(start2, end)));
217 }
218 } else if (this.sub.length > 0) {
219 let first = this.sub[0];
220 let last = this.sub[this.sub.length - 1];
221 this.toHexDOM_sub(node, 'intro', this.stream, this.posContent(), first.posStart());
222 for (let i = 0, max = this.sub.length; i < max; ++i)
223 node.appendChild(this.sub[i].toHexDOM(root, trim));
224 this.toHexDOM_sub(node, 'outro', this.stream, last.posEnd(), this.posEnd());
225 } else
226 this.toHexDOM_sub(node, 'outro', this.stream, this.posContent(), this.posEnd());
227 return node;
228 }
229 static decode(stream, offset) {
230 return ASN1.decode(stream, offset, ASN1DOM);
231 }
232
233}
234
235return ASN1DOM;
236
237});