JavaScript generic ASN.1 parser (mirror)
at github-101 7.2 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 let ul = document.createElement('ul'); 45 ul.className = 'treecollapse'; 46 tree.appendChild(ul); 47 ul.appendChild(asn1.toDOM()); 48 if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked)); 49} 50export function decode(der, offset) { 51 offset = offset || 0; 52 try { 53 const asn1 = ASN1DOM.decode(der, offset); 54 if (wantDef.checked) { 55 selectDefs.innerHTML = ''; 56 const types = Defs.commonTypes 57 .map(type => { 58 const stats = Defs.match(asn1, type); 59 return { type, match: stats.recognized / stats.total }; 60 }) 61 .sort((a, b) => b.match - a.match); 62 for (const t of types) { 63 t.element = document.createElement('option'); 64 t.element.innerText = (t.match * 100).toFixed(1) + '% ' + t.type.description; 65 selectDefs.appendChild(t.element); 66 } 67 let not = document.createElement('option'); 68 not.innerText = 'no definition'; 69 selectDefs.appendChild(not); 70 Defs.match(asn1, types[0].type); 71 selectDefs.onchange = () => { 72 for (const t of types) { 73 if (t.element == selectDefs.selectedOptions[0]) { 74 Defs.match(asn1, t.type); 75 show(asn1); 76 return; 77 } 78 } 79 Defs.match(asn1, null); 80 show(asn1); 81 }; 82 } else 83 selectDefs.innerHTML = '<option>no definition</option>'; 84 show(asn1); 85 let b64 = der.length < maxLength ? asn1.toB64String() : ''; 86 if (area.value === '') area.value = Base64.pretty(b64); 87 try { 88 window.location.hash = hash = '#' + b64; 89 } catch (e) { 90 // fails with "Access Denied" on IE with URLs longer than ~2048 chars 91 window.location.hash = hash = '#'; 92 } 93 let endOffset = asn1.posEnd(); 94 if (endOffset < der.length) { 95 let p = document.createElement('p'); 96 p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.'; 97 let button = document.createElement('button'); 98 button.innerText = 'try to decode'; 99 button.onclick = function () { 100 decode(der, endOffset); 101 }; 102 p.appendChild(button); 103 tree.insertBefore(p, tree.firstChild); 104 } 105 } catch (e) { 106 text(tree, e); 107 } 108} 109export function decodeText(val) { 110 try { 111 let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val); 112 decode(der); 113 } catch (e) { 114 text(tree, e); 115 dump.innerHTML = ''; 116 } 117} 118export function decodeBinaryString(str) { 119 let der; 120 try { 121 if (reHex.test(str)) der = Hex.decode(str); 122 else if (Base64.re.test(str)) der = Base64.unarmor(str); 123 else der = str; 124 decode(der); 125 } catch (e) { 126 text(tree, 'Cannot decode file.'); 127 dump.innerHTML = ''; 128 } 129} 130// set up buttons 131const butClickHandlers = { 132 butDecode: () => { 133 decodeText(area.value); 134 }, 135 butClear: () => { 136 area.value = ''; 137 file.value = ''; 138 tree.innerHTML = ''; 139 dump.innerHTML = ''; 140 selectDefs.innerHTML = ''; 141 hash = window.location.hash = ''; 142 }, 143 butExample: () => { 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}; 159for (const [name, onClick] of Object.entries(butClickHandlers)) { 160 let elem = id(name); 161 if (elem) 162 elem.onclick = onClick; 163} 164// this is only used if window.FileReader 165function read(f) { 166 area.value = ''; // clear text area, will get b64 content 167 let r = new FileReader(); 168 r.onloadend = function () { 169 if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').'); 170 else decodeBinaryString(r.result); 171 }; 172 r.readAsBinaryString(f); 173} 174function load() { 175 if (file.files.length === 0) alert('Select a file to load first.'); 176 else read(file.files[0]); 177} 178function loadFromHash() { 179 if (window.location.hash && window.location.hash != hash) { 180 hash = window.location.hash; 181 // Firefox is not consistent with other browsers and returns an 182 // already-decoded hash string so we risk double-decoding here, 183 // but since % is not allowed in base64 nor hexadecimal, it's ok 184 let val = decodeURIComponent(hash.substr(1)); 185 if (val.length) decodeText(val); 186 } 187} 188function stop(e) { 189 e.stopPropagation(); 190 e.preventDefault(); 191} 192function dragAccept(e) { 193 stop(e); 194 if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]); 195} 196// main 197if ('onhashchange' in window) window.onhashchange = loadFromHash; 198loadFromHash(); 199document.ondragover = stop; 200document.ondragleave = stop; 201if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) { 202 file.style.display = 'block'; 203 file.onchange = load; 204 document.ondrop = dragAccept; 205} 206for (let tag in tags) { 207 let date = tags[tag]; 208 let el = document.createElement('option'); 209 el.value = tag; 210 el.innerText = date + ' ' + tag; 211 selectTag.appendChild(el); 212} 213selectTag.onchange = function (ev) { 214 let tag = ev.target.selectedOptions[0].value; 215 window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html'; 216};