JavaScript generic ASN.1 parser (mirror)
at github-90 213 lines 7.1 kB view raw
1import './theme.js'; 2import { ASN1DOM } from './dom.js'; 3import { Base64 } from './base64.js'; 4import { Hex } from './hex.js'; 5import { Defs } from './defs.js'; 6import { tags } from './tags.js'; 7 8const 9 maxLength = 10240, 10 reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/, 11 tree = id('tree'), 12 dump = id('dump'), 13 wantHex = checkbox('wantHex'), 14 trimHex = checkbox('trimHex'), 15 wantDef = checkbox('wantDef'), 16 area = id('area'), 17 file = id('file'), 18 examples = id('examples'), 19 selectDefs = id('definitions'), 20 selectTag = id('tags'); 21 22let hash = null; 23 24if (!window.console || !window.console.log) // IE8 with closed developer tools 25 window.console = { log: function () {} }; 26function id(elem) { 27 return document.getElementById(elem); 28} 29function text(el, string) { 30 if ('textContent' in el) el.textContent = string; 31 else el.innerText = string; 32} 33function checkbox(name) { 34 const el = id(name); 35 const cfg = localStorage.getItem(name); 36 if (cfg === 'false') 37 el.checked = false; 38 el.onchange = () => localStorage.setItem(name, el.checked); 39 return el; 40} 41function show(asn1) { 42 tree.innerHTML = ''; 43 dump.innerHTML = ''; 44 tree.appendChild(asn1.toDOM()); 45 if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked)); 46} 47export function decode(der, offset) { 48 offset = offset || 0; 49 try { 50 const asn1 = ASN1DOM.decode(der, offset); 51 if (wantDef.checked) { 52 selectDefs.innerHTML = ''; 53 const types = Defs.commonTypes 54 .map(type => { 55 const stats = Defs.match(asn1, type); 56 return { type, match: stats.recognized / stats.total }; 57 }) 58 .sort((a, b) => b.match - a.match); 59 for (const t of types) { 60 t.element = document.createElement('option'); 61 t.element.innerText = (t.match * 100).toFixed(1) + '% ' + t.type.description; 62 selectDefs.appendChild(t.element); 63 } 64 let not = document.createElement('option'); 65 not.innerText = 'no definition'; 66 selectDefs.appendChild(not); 67 Defs.match(asn1, types[0].type); 68 selectDefs.onchange = () => { 69 for (const t of types) { 70 if (t.element == selectDefs.selectedOptions[0]) { 71 Defs.match(asn1, t.type); 72 show(asn1); 73 return; 74 } 75 } 76 Defs.match(asn1, null); 77 show(asn1); 78 }; 79 } else 80 selectDefs.innerHTML = '<option>no definition</option>'; 81 show(asn1); 82 let b64 = der.length < maxLength ? asn1.toB64String() : ''; 83 if (area.value === '') area.value = Base64.pretty(b64); 84 try { 85 window.location.hash = hash = '#' + b64; 86 } catch (e) { 87 // fails with "Access Denied" on IE with URLs longer than ~2048 chars 88 window.location.hash = hash = '#'; 89 } 90 let endOffset = asn1.posEnd(); 91 if (endOffset < der.length) { 92 let p = document.createElement('p'); 93 p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.'; 94 let button = document.createElement('button'); 95 button.innerText = 'try to decode'; 96 button.onclick = function () { 97 decode(der, endOffset); 98 }; 99 p.appendChild(button); 100 tree.insertBefore(p, tree.firstChild); 101 } 102 } catch (e) { 103 text(tree, e); 104 } 105} 106export function decodeText(val) { 107 try { 108 let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val); 109 decode(der); 110 } catch (e) { 111 text(tree, e); 112 dump.innerHTML = ''; 113 } 114} 115export function decodeBinaryString(str) { 116 let der; 117 try { 118 if (reHex.test(str)) der = Hex.decode(str); 119 else if (Base64.re.test(str)) der = Base64.unarmor(str); 120 else der = str; 121 decode(der); 122 } catch (e) { 123 text(tree, 'Cannot decode file.'); 124 dump.innerHTML = ''; 125 } 126} 127// set up buttons 128const butClickHandlers = { 129 butDecode: () => { 130 decodeText(area.value); 131 }, 132 butClear: () => { 133 area.value = ''; 134 file.value = ''; 135 tree.innerHTML = ''; 136 dump.innerHTML = ''; 137 selectDefs.innerHTML = ''; 138 hash = window.location.hash = ''; 139 }, 140 butExample: () => { 141 console.log('Loading example:', examples.value); 142 let request = new XMLHttpRequest(); 143 request.open('GET', 'examples/' + examples.value, true); 144 request.onreadystatechange = function () { 145 if (this.readyState !== 4) return; 146 if (this.status >= 200 && this.status < 400) { 147 area.value = this.responseText; 148 decodeText(this.responseText); 149 } else { 150 console.log('Error loading example.'); 151 } 152 }; 153 request.send(); 154 }, 155}; 156for (const [name, onClick] of Object.entries(butClickHandlers)) { 157 let elem = id(name); 158 if (elem) 159 elem.onclick = onClick; 160} 161// this is only used if window.FileReader 162function read(f) { 163 area.value = ''; // clear text area, will get b64 content 164 let r = new FileReader(); 165 r.onloadend = function () { 166 if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').'); 167 else decodeBinaryString(r.result); 168 }; 169 r.readAsBinaryString(f); 170} 171function load() { 172 if (file.files.length === 0) alert('Select a file to load first.'); 173 else read(file.files[0]); 174} 175function loadFromHash() { 176 if (window.location.hash && window.location.hash != hash) { 177 hash = window.location.hash; 178 // Firefox is not consistent with other browsers and returns an 179 // already-decoded hash string so we risk double-decoding here, 180 // but since % is not allowed in base64 nor hexadecimal, it's ok 181 let val = decodeURIComponent(hash.substr(1)); 182 if (val.length) decodeText(val); 183 } 184} 185function stop(e) { 186 e.stopPropagation(); 187 e.preventDefault(); 188} 189function dragAccept(e) { 190 stop(e); 191 if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]); 192} 193// main 194if ('onhashchange' in window) window.onhashchange = loadFromHash; 195loadFromHash(); 196document.ondragover = stop; 197document.ondragleave = stop; 198if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) { 199 file.style.display = 'block'; 200 file.onchange = load; 201 document.ondrop = dragAccept; 202} 203for (let tag in tags) { 204 let date = tags[tag]; 205 let el = document.createElement('option'); 206 el.value = tag; 207 el.innerText = date + ' ' + tag; 208 selectTag.appendChild(el); 209} 210selectTag.onchange = function (ev) { 211 let tag = ev.target.selectedOptions[0].value; 212 window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html'; 213};