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