/ index.js
index.js
  1  import './theme.js';
  2  import { ASN1DOM } from './dom.js';
  3  import { Base64 } from './base64.js';
  4  import { Hex } from './hex.js';
  5  import { Defs } from './defs.js';
  6  import { tags } from './tags.js';
  7  
  8  const
  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  
 22  let hash = null;
 23  
 24  if (!window.console || !window.console.log) // IE8 with closed developer tools
 25      window.console = { log: function () {} };
 26  function id(elem) {
 27      return document.getElementById(elem);
 28  }
 29  function text(el, string) {
 30      if ('textContent' in el) el.textContent = string;
 31      else el.innerText = string;
 32  }
 33  function 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  }
 41  function 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  }
 50  export 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 (ignore) {
 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  }
109  export 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  }
118  export 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 (ignore) {
126          text(tree, 'Cannot decode file.');
127          dump.innerHTML = '';
128      }
129  }
130  // set up buttons
131  const 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  };
159  for (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
165  function 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  }
174  function load() {
175      if (file.files.length === 0) alert('Select a file to load first.');
176      else read(file.files[0]);
177  }
178  function 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  }
188  function stop(e) {
189      e.stopPropagation();
190      e.preventDefault();
191  }
192  function dragAccept(e) {
193      stop(e);
194      if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
195  }
196  // main
197  if ('onhashchange' in window) window.onhashchange = loadFromHash;
198  loadFromHash();
199  document.ondragover = stop;
200  document.ondragleave = stop;
201  if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
202      file.style.display = 'block';
203      file.onchange = load;
204      document.ondrop = dragAccept;
205  }
206  for (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  }
213  selectTag.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  };