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