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};