1// ASN.1 JavaScript decoder
2// Copyright (c) 2008-2022 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') factory(function (name) { return require(name); });
18 else factory(function (name) { return window[name.substring(2)]; });
19})(function (require) {
20"use strict";
21
22var ASN1 = require('./asn1'),
23 oids = require('./oids'),
24 lineLength = 80,
25 contentLength = 8 * lineLength,
26 DOM = {
27 ellipsis: "\u2026",
28 tag: function (tagName, className) {
29 var t = document.createElement(tagName);
30 t.className = className;
31 return t;
32 },
33 text: function (str) {
34 return document.createTextNode(str);
35 },
36 space: function () {
37 var t = document.createElement('span');
38 t.className = 'spaces';
39 t.innerHTML = ' ';
40 return t;
41 },
42 breakLines: function (str, length) {
43 var lines = str.split(/\r?\n/),
44 o = '';
45 for (var i = 0; i < lines.length; ++i) {
46 var line = lines[i];
47 if (i > 0) o += "\n";
48 while (line.length > length) {
49 o += line.substring(0, length);
50 o += "\n";
51 line = line.substring(length);
52 }
53 o += line;
54 }
55 return o;
56 }
57 };
58
59ASN1.prototype.toDOM = function (spaces) {
60 spaces = spaces || '';
61 var isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
62 var node = DOM.tag("div", "node");
63 node.asn1 = this;
64 var head = DOM.tag("div", "head");
65 head.innerHTML = "<span class='spaces'>" + spaces + "</span>" + this.typeName().replace(/_/g, " ");
66 var content = this.content(contentLength);
67 if (content !== null) {
68 var preview = DOM.tag("span", "preview"),
69 shortContent;
70 if (isOID)
71 content = content.split('\n', 1)[0];
72 shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content;
73 preview.appendChild(DOM.space());
74 preview.appendChild(DOM.text(shortContent));
75 if (isOID) {
76 var oid = oids[content];
77 if (oid) {
78 if (oid.d) {
79 preview.appendChild(DOM.space());
80 var oidd = DOM.tag("span", "oid description");
81 oidd.appendChild(DOM.text(oid.d));
82 preview.appendChild(oidd);
83 }
84 if (oid.c) {
85 preview.appendChild(DOM.space());
86 var oidc = DOM.tag("span", "oid comment");
87 oidc.appendChild(DOM.text("(" + oid.c + ")"));
88 preview.appendChild(oidc);
89 }
90 }
91 }
92 head.appendChild(preview);
93 content = DOM.breakLines(content, lineLength);
94 content = content.replace(/</g, "<");
95 content = content.replace(/\n/g, "<br>");
96 }
97 node.appendChild(head);
98 this.node = node;
99 this.head = head;
100 var value = DOM.tag("div", "value");
101 var s = "Offset: " + this.stream.pos + "<br>";
102 s += "Length: " + this.header + "+";
103 if (this.length >= 0)
104 s += this.length;
105 else
106 s += (-this.length) + " (undefined)";
107 if (this.tag.tagConstructed)
108 s += "<br>(constructed)";
109 else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
110 s += "<br>(encapsulates)";
111 //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: "
112 if (content !== null) {
113 s += "<br>Value:<br><b>" + content + "</b>";
114 if (isOID && oid) {
115 if (oid.d) s += "<br>" + oid.d;
116 if (oid.c) s += "<br>" + oid.c;
117 if (oid.w) s += "<br>(warning!)";
118 }
119 }
120 value.innerHTML = s;
121 node.appendChild(value);
122 var sub = DOM.tag("div", "sub");
123 if (this.sub !== null) {
124 spaces += '\xA0 ';
125 for (var i = 0, max = this.sub.length; i < max; ++i)
126 sub.appendChild(this.sub[i].toDOM(spaces));
127 }
128 node.appendChild(sub);
129 head.onclick = function () {
130 node.className = (node.className == "node collapsed") ? "node" : "node collapsed";
131 };
132 return node;
133};
134ASN1.prototype.fakeHover = function (current) {
135 this.node.className += " hover";
136 if (current)
137 this.head.className += " hover";
138};
139ASN1.prototype.fakeOut = function (current) {
140 var re = / ?hover/;
141 this.node.className = this.node.className.replace(re, "");
142 if (current)
143 this.head.className = this.head.className.replace(re, "");
144};
145ASN1.prototype.toHexDOM_sub = function (node, className, stream, start, end) {
146 if (start >= end)
147 return;
148 var sub = DOM.tag("span", className);
149 sub.appendChild(DOM.text(
150 stream.hexDump(start, end)));
151 node.appendChild(sub);
152};
153ASN1.prototype.toHexDOM = function (root) {
154 var node = DOM.tag("span", "hex");
155 if (root === undefined) root = node;
156 this.head.hexNode = node;
157 this.head.onmouseover = function () { this.hexNode.className = "hexCurrent"; };
158 this.head.onmouseout = function () { this.hexNode.className = "hex"; };
159 node.asn1 = this;
160 node.onmouseover = function () {
161 var current = !root.selected;
162 if (current) {
163 root.selected = this.asn1;
164 this.className = "hexCurrent";
165 }
166 this.asn1.fakeHover(current);
167 };
168 node.onmouseout = function () {
169 var current = (root.selected == this.asn1);
170 this.asn1.fakeOut(current);
171 if (current) {
172 root.selected = null;
173 this.className = "hex";
174 }
175 };
176 if (root == node) {
177 var lineStart = this.posStart() & 0xF;
178 if (lineStart != 0) {
179 var skip = DOM.tag("span", "skip");
180 var skipStr = '';
181 for (var j = lineStart; j > 0; --j)
182 skipStr += ' ';
183 if (lineStart >= 8)
184 skipStr += ' ';
185 skip.innerText = skipStr;
186 node.appendChild(skip);
187 }
188 }
189 this.toHexDOM_sub(node, "tag", this.stream, this.posStart(), this.posLen());
190 this.toHexDOM_sub(node, (this.length >= 0) ? "dlen" : "ulen", this.stream, this.posLen(), this.posContent());
191 if (this.sub === null) {
192 var start = this.posContent();
193 var end = this.posEnd();
194 if (end - start < 10 * 16)
195 node.appendChild(DOM.text(
196 this.stream.hexDump(start, end)));
197 else {
198 var end1 = start + 5 * 16 - (start & 0xF);
199 var start2 = end - 16 - (end & 0xF);
200 node.appendChild(DOM.text(
201 this.stream.hexDump(start, end1)));
202 var sub = DOM.tag("span", "skip");
203 sub.appendChild(DOM.text("\u2026 skipping " + (start2 - end1) + " bytes \u2026\n"));
204 node.appendChild(sub);
205 node.appendChild(DOM.text(
206 this.stream.hexDump(start2, end)));
207 }
208 } else if (this.sub.length > 0) {
209 var first = this.sub[0];
210 var last = this.sub[this.sub.length - 1];
211 this.toHexDOM_sub(node, "intro", this.stream, this.posContent(), first.posStart());
212 for (var i = 0, max = this.sub.length; i < max; ++i)
213 node.appendChild(this.sub[i].toHexDOM(root));
214 this.toHexDOM_sub(node, "outro", this.stream, last.posEnd(), this.posEnd());
215 } else
216 this.toHexDOM_sub(node, "outro", this.stream, this.posContent(), this.posEnd());
217 return node;
218};
219
220});