JavaScript generic ASN.1 parser (mirror)
at github-101 9.9 kB view raw
1// ASN.1 JavaScript decoder 2// Copyright (c) 2008 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 if (className) 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('li'); 60 node.asn1 = this; 61 let head = DOM.tag('span', 'head'); 62 const typeName = this.typeName().replace(/_/g, ' '); 63 if (this.def) { 64 if (this.def.id) { 65 head.appendChild(DOM.tag('span', 'name id', this.def.id)); 66 head.appendChild(DOM.space()); 67 } 68 if (this.def.name && this.def.name != typeName) { 69 head.appendChild(DOM.tag('span', 'name type', this.def.name)); 70 head.appendChild(DOM.space()); 71 } 72 if (this.def.mismatch) { 73 head.appendChild(DOM.tag('span', 'name type', '[?]')); 74 head.appendChild(DOM.space()); 75 } 76 } 77 head.appendChild(DOM.text(typeName)); 78 let content; 79 try { 80 content = this.content(contentLength); 81 } catch (e) { 82 content = 'Cannot decode: ' + e; 83 } 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, '&lt;'); 111 content = content.replace(/\n/g, '<br>'); 112 } 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; 131 this.head = head; 132 let value = DOM.tag('div', 'value'); 133 let s = 'Offset: ' + this.stream.pos + '<br>'; 134 s += 'Length: ' + this.header + '+'; 135 if (this.length >= 0) 136 s += this.length; 137 else 138 s += (-this.length) + ' (undefined)'; 139 if (this.tag.tagConstructed) 140 s += '<br>(constructed)'; 141 else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) 142 s += '<br>(encapsulates)'; 143 //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: " 144 if (content !== null) { 145 s += '<br>Value:<br><b>' + content + '</b>'; 146 if (isOID && oid) { 147 if (oid.d) s += '<br>' + oid.d; 148 if (oid.c) s += '<br>' + oid.c; 149 if (oid.w) s += '<br>(warning!)'; 150 } 151 } 152 value.innerHTML = s; 153 contentNode.appendChild(value); 154 if (this.sub !== null) { 155 let sub = DOM.tag('ul'); 156 childNode.appendChild(sub); 157 spaces += '\xA0 '; 158 for (let i = 0, max = this.sub.length; i < max; ++i) 159 sub.appendChild(this.sub[i].toDOM(spaces)); 160 } 161 bindContextMenu(node); 162 return node; 163 } 164 fakeHover(current) { 165 this.node.classList.add('hover'); 166 if (current) 167 this.head.classList.add('hover'); 168 } 169 fakeOut(current) { 170 this.node.classList.remove('hover'); 171 if (current) 172 this.head.classList.remove('hover'); 173 } 174 toHexDOM_sub(node, className, stream, start, end) { 175 if (start >= end) 176 return; 177 let sub = DOM.tag('span', className, stream.hexDump(start, end)); 178 node.appendChild(sub); 179 } 180 toHexDOM(root, trim=true) { 181 let node = DOM.tag('span', 'hex'); 182 if (root === undefined) root = node; 183 this.head.hexNode = node; 184 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; }; 185 this.head.onmouseout = function () { this.hexNode.className = 'hex'; }; 186 node.asn1 = this; 187 node.onmouseover = function (event) { 188 let current = !root.selected; 189 if (current) { 190 root.selected = this.asn1; 191 this.className = 'hexCurrent'; 192 } 193 this.asn1.fakeHover(current); 194 event.stopPropagation(); 195 }; 196 node.onmouseout = function () { 197 let current = (root.selected == this.asn1); 198 this.asn1.fakeOut(current); 199 if (current) { 200 root.selected = null; 201 this.className = 'hex'; 202 } 203 }; 204 bindContextMenu(node); 205 if (root == node) { 206 let lineStart = this.posStart() & 0xF; 207 if (lineStart != 0) { 208 let skip = DOM.tag('span', 'skip'); 209 let skipStr = ''; 210 for (let j = lineStart; j > 0; --j) 211 skipStr += ' '; 212 if (lineStart >= 8) 213 skipStr += ' '; 214 skip.innerText = skipStr; 215 node.appendChild(skip); 216 } 217 } 218 this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen()); 219 this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent()); 220 if (this.sub === null) { 221 let start = this.posContent(); 222 let end = this.posEnd(); 223 if (!trim || end - start < 10 * 16) 224 node.appendChild(DOM.text( 225 this.stream.hexDump(start, end))); 226 else { 227 let end1 = start + 5 * 16 - (start & 0xF); 228 let start2 = end - 16 - (end & 0xF); 229 node.appendChild(DOM.text(this.stream.hexDump(start, end1))); 230 node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n')); 231 node.appendChild(DOM.text(this.stream.hexDump(start2, end))); 232 } 233 } else if (this.sub.length > 0) { 234 let first = this.sub[0]; 235 let last = this.sub[this.sub.length - 1]; 236 this.toHexDOM_sub(node, 'intro', this.stream, this.posContent(), first.posStart()); 237 for (let i = 0, max = this.sub.length; i < max; ++i) 238 node.appendChild(this.sub[i].toHexDOM(root, trim)); 239 this.toHexDOM_sub(node, 'outro', this.stream, last.posEnd(), this.posEnd()); 240 } else 241 this.toHexDOM_sub(node, 'outro', this.stream, this.posContent(), this.posEnd()); 242 return node; 243 } 244 static decode(stream, offset) { 245 return ASN1.decode(stream, offset, ASN1DOM); 246 } 247 248}