JavaScript generic ASN.1 parser (mirror)
at github-82 233 lines 7.7 kB view raw
1import { ASN1DOM } from './dom.js'; 2import { Base64 } from './base64.js'; 3import { Hex } from './hex.js'; 4import { Defs } from './defs.js'; 5import { tags } from './tags.js'; 6 7const 8 maxLength = 10240, 9 reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/, 10 tree = id('tree'), 11 dump = id('dump'), 12 wantHex = checkbox('wantHex'), 13 trimHex = checkbox('trimHex'), 14 wantDef = checkbox('wantDef'), 15 area = id('area'), 16 file = id('file'), 17 examples = id('examples'), 18 selectTheme = id('theme-select'), 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} 47function 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} 106function 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} 115function 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 128id('butDecode').onclick = function () { 129 decodeText(area.value); 130}; 131id('butClear').onclick = function () { 132 area.value = ''; 133 file.value = ''; 134 tree.innerHTML = ''; 135 dump.innerHTML = ''; 136 selectDefs.innerHTML = ''; 137 hash = window.location.hash = ''; 138}; 139id('butExample').onclick = function () { 140 console.log('Loading example:', examples.value); 141 let request = new XMLHttpRequest(); 142 request.open('GET', 'examples/' + examples.value, true); 143 request.onreadystatechange = function () { 144 if (this.readyState !== 4) return; 145 if (this.status >= 200 && this.status < 400) { 146 area.value = this.responseText; 147 decodeText(this.responseText); 148 } else { 149 console.log('Error loading example.'); 150 } 151 }; 152 request.send(); 153}; 154// set dark theme depending on OS settings 155function setTheme() { 156 let storedTheme = localStorage.getItem('theme'); 157 let theme = 'os'; 158 if (storedTheme) 159 theme = storedTheme; 160 selectTheme.value = theme; 161 if (theme == 'os') { 162 let prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); 163 theme = prefersDarkScheme.matches ? 'dark': 'light'; 164 } 165 if (theme == 'dark') { 166 const css1 = id('theme-base'); 167 const css2 = css1.cloneNode(); 168 css2.id = 'theme-override'; 169 css2.href = 'index-' + theme + '.css'; 170 css1.parentElement.appendChild(css2); 171 } else { 172 const css2 = id('theme-override'); 173 if (css2) css2.remove(); 174 } 175} 176setTheme(); 177selectTheme.addEventListener('change', function () { 178 localStorage.setItem('theme', selectTheme.value); 179 setTheme(); 180}); 181// this is only used if window.FileReader 182function read(f) { 183 area.value = ''; // clear text area, will get b64 content 184 let r = new FileReader(); 185 r.onloadend = function () { 186 if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').'); 187 else decodeBinaryString(r.result); 188 }; 189 r.readAsBinaryString(f); 190} 191function load() { 192 if (file.files.length === 0) alert('Select a file to load first.'); 193 else read(file.files[0]); 194} 195function loadFromHash() { 196 if (window.location.hash && window.location.hash != hash) { 197 hash = window.location.hash; 198 // Firefox is not consistent with other browsers and returns an 199 // already-decoded hash string so we risk double-decoding here, 200 // but since % is not allowed in base64 nor hexadecimal, it's ok 201 let val = decodeURIComponent(hash.substr(1)); 202 if (val.length) decodeText(val); 203 } 204} 205function stop(e) { 206 e.stopPropagation(); 207 e.preventDefault(); 208} 209function dragAccept(e) { 210 stop(e); 211 if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]); 212} 213// main 214if ('onhashchange' in window) window.onhashchange = loadFromHash; 215loadFromHash(); 216document.ondragover = stop; 217document.ondragleave = stop; 218if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) { 219 file.style.display = 'block'; 220 file.onchange = load; 221 document.ondrop = dragAccept; 222} 223for (let tag in tags) { 224 let date = tags[tag]; 225 let el = document.createElement('option'); 226 el.value = tag; 227 el.innerText = date + ' ' + tag; 228 selectTag.appendChild(el); 229} 230selectTag.onchange = function (ev) { 231 let tag = ev.target.selectedOptions[0].value; 232 window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html'; 233};