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 tree.appendChild(asn1.toDOM());
45 if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked));
46}
47export function 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}
106export function 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}
115export function 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
128const butClickHandlers = {
129 butDecode: () => {
130 decodeText(area.value);
131 },
132 butClear: () => {
133 area.value = '';
134 file.value = '';
135 tree.innerHTML = '';
136 dump.innerHTML = '';
137 selectDefs.innerHTML = '';
138 hash = window.location.hash = '';
139 },
140 butExample: () => {
141 console.log('Loading example:', examples.value);
142 let request = new XMLHttpRequest();
143 request.open('GET', 'examples/' + examples.value, true);
144 request.onreadystatechange = function () {
145 if (this.readyState !== 4) return;
146 if (this.status >= 200 && this.status < 400) {
147 area.value = this.responseText;
148 decodeText(this.responseText);
149 } else {
150 console.log('Error loading example.');
151 }
152 };
153 request.send();
154 },
155};
156for (const [name, onClick] of Object.entries(butClickHandlers)) {
157 let elem = id(name);
158 if (elem)
159 elem.onclick = onClick;
160}
161// this is only used if window.FileReader
162function read(f) {
163 area.value = ''; // clear text area, will get b64 content
164 let r = new FileReader();
165 r.onloadend = function () {
166 if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').');
167 else decodeBinaryString(r.result);
168 };
169 r.readAsBinaryString(f);
170}
171function load() {
172 if (file.files.length === 0) alert('Select a file to load first.');
173 else read(file.files[0]);
174}
175function loadFromHash() {
176 if (window.location.hash && window.location.hash != hash) {
177 hash = window.location.hash;
178 // Firefox is not consistent with other browsers and returns an
179 // already-decoded hash string so we risk double-decoding here,
180 // but since % is not allowed in base64 nor hexadecimal, it's ok
181 let val = decodeURIComponent(hash.substr(1));
182 if (val.length) decodeText(val);
183 }
184}
185function stop(e) {
186 e.stopPropagation();
187 e.preventDefault();
188}
189function dragAccept(e) {
190 stop(e);
191 if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
192}
193// main
194if ('onhashchange' in window) window.onhashchange = loadFromHash;
195loadFromHash();
196document.ondragover = stop;
197document.ondragleave = stop;
198if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
199 file.style.display = 'block';
200 file.onchange = load;
201 document.ondrop = dragAccept;
202}
203for (let tag in tags) {
204 let date = tags[tag];
205 let el = document.createElement('option');
206 el.value = tag;
207 el.innerText = date + ' ' + tag;
208 selectTag.appendChild(el);
209}
210selectTag.onchange = function (ev) {
211 let tag = ev.target.selectedOptions[0].value;
212 window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html';
213};