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