/ dom.js
dom.js
  1  // ASN.1 JavaScript decoder
  2  // Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
  3  
  4  // Permission to use, copy, modify, and/or distribute this software for any
  5  // purpose with or without fee is hereby granted, provided that the above
  6  // copyright notice and this permission notice appear in all copies.
  7  //
  8  // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9  // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10  // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11  // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12  // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13  // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14  // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15  
 16  import { ASN1 } from './asn1.js';
 17  import { oids } from './oids.js';
 18  import { bindContextMenu } from './context.js';
 19  
 20  const
 21      lineLength = 80,
 22      contentLength = 8 * lineLength,
 23      DOM = {
 24          ellipsis: '\u2026',
 25          tag: function (tagName, className, text) {
 26              let t = document.createElement(tagName);
 27              if (className) t.className = className;
 28              if (text) t.innerText = text;
 29              return t;
 30          },
 31          text: function (str) {
 32              return document.createTextNode(str);
 33          },
 34          space: function () {
 35              return DOM.tag('span', 'spaces', ' ');
 36          },
 37          breakLines: function (str, length) {
 38              let lines = str.split(/\r?\n/),
 39                  o = '';
 40              for (let i = 0; i < lines.length; ++i) {
 41                  let line = lines[i];
 42                  if (i > 0) o += '\n';
 43                  while (line.length > length) {
 44                      o += line.substring(0, length);
 45                      o += '\n';
 46                      line = line.substring(length);
 47                  }
 48                  o += line;
 49              }
 50              return o;
 51          },
 52      };
 53  
 54  export class ASN1DOM extends ASN1 {
 55  
 56      toDOM(spaces) {
 57          spaces = spaces || '';
 58          let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
 59          let node = DOM.tag('li');
 60          node.asn1 = this;
 61          let head = DOM.tag('span', 'head');
 62          const typeName = this.typeName().replace(/_/g, ' ');
 63          if (this.def) {
 64              if (this.def.id) {
 65                  head.appendChild(DOM.tag('span', 'name id', this.def.id));
 66                  head.appendChild(DOM.space());
 67              }
 68              if (this.def.name && this.def.name != typeName) {
 69                  head.appendChild(DOM.tag('span', 'name type', this.def.name));
 70                  head.appendChild(DOM.space());
 71              }
 72              if (this.def.mismatch) {
 73                  head.appendChild(DOM.tag('span', 'name type', '[?]'));
 74                  head.appendChild(DOM.space());
 75              }
 76          }
 77          head.appendChild(DOM.text(typeName));
 78          let content;
 79          try {
 80              content = this.content(contentLength);
 81          } catch (e) {
 82              content = 'Cannot decode: ' + e;
 83          }
 84          let oid;
 85          if (content !== null) {
 86              let preview = DOM.tag('span', 'preview'),
 87                  shortContent;
 88              if (isOID)
 89                  content = content.split('\n', 1)[0];
 90              shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content;
 91              preview.appendChild(DOM.space());
 92              preview.appendChild(DOM.text(shortContent));
 93              if (isOID) {
 94                  oid = oids[content];
 95                  if (oid) {
 96                      if (oid.d) {
 97                          preview.appendChild(DOM.space());
 98                          let oidd = DOM.tag('span', 'oid description', oid.d);
 99                          preview.appendChild(oidd);
100                      }
101                      if (oid.c) {
102                          preview.appendChild(DOM.space());
103                          let oidc = DOM.tag('span', 'oid comment', '(' + oid.c + ')');
104                          preview.appendChild(oidc);
105                      }
106                  }
107              }
108              head.appendChild(preview);
109              content = DOM.breakLines(content, lineLength);
110              content = content.replace(/</g, '&lt;');
111              content = content.replace(/\n/g, '<br>');
112          }
113          // add the li and details section for this node
114          let contentNode;
115          let childNode;
116          if (this.sub !== null) {
117              let details = DOM.tag('details');
118              details.setAttribute('open', '');
119              node.appendChild(details);
120              let summary = DOM.tag('summary', 'node');
121              details.appendChild(summary);
122              summary.appendChild(head);
123              contentNode = summary;
124              childNode = details;
125          } else {
126              contentNode = node;
127              contentNode.classList.add('node');
128              contentNode.appendChild(head);
129          }
130          this.node = contentNode;
131          this.head = head;
132          let value = DOM.tag('div', 'value');
133          let s = 'Offset: ' + this.stream.pos + '<br>';
134          s += 'Length: ' + this.header + '+';
135          if (this.length >= 0)
136              s += this.length;
137          else
138              s += (-this.length) + ' (undefined)';
139          if (this.tag.tagConstructed)
140              s += '<br>(constructed)';
141          else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
142              s += '<br>(encapsulates)';
143          //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: "
144          if (content !== null) {
145              s += '<br>Value:<br><b>' + content + '</b>';
146              if (isOID && oid) {
147                  if (oid.d) s += '<br>' + oid.d;
148                  if (oid.c) s += '<br>' + oid.c;
149                  if (oid.w) s += '<br>(warning!)';
150              }
151          }
152          value.innerHTML = s;
153          contentNode.appendChild(value);
154          if (this.sub !== null) {
155              let sub = DOM.tag('ul');
156              childNode.appendChild(sub);
157              spaces += '\xA0 ';
158              for (let i = 0, max = this.sub.length; i < max; ++i)
159                  sub.appendChild(this.sub[i].toDOM(spaces));
160          }
161          bindContextMenu(node);
162          return node;
163      }
164      fakeHover(current) {
165          this.node.classList.add('hover');
166          if (current)
167              this.head.classList.add('hover');
168      }
169      fakeOut(current) {
170          this.node.classList.remove('hover');
171          if (current)
172              this.head.classList.remove('hover');
173      }
174      toHexDOM_sub(node, className, stream, start, end) {
175          if (start >= end)
176              return;
177          let sub = DOM.tag('span', className, stream.hexDump(start, end));
178          node.appendChild(sub);
179      }
180      toHexDOM(root, trim=true) {
181          let node = DOM.tag('span', 'hex');
182          if (root === undefined) root = node;
183          this.head.hexNode = node;
184          this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
185          this.head.onmouseout  = function () { this.hexNode.className = 'hex'; };
186          node.asn1 = this;
187          node.onmouseover = function (event) {
188              let current = !root.selected;
189              if (current) {
190                  root.selected = this.asn1;
191                  this.className = 'hexCurrent';
192              }
193              this.asn1.fakeHover(current);
194              event.stopPropagation();
195          };
196          node.onmouseout = function () {
197              let current = (root.selected == this.asn1);
198              this.asn1.fakeOut(current);
199              if (current) {
200                  root.selected = null;
201                  this.className = 'hex';
202              }
203          };
204          bindContextMenu(node);
205          if (root == node) {
206              let lineStart = this.posStart() & 0xF;
207              if (lineStart != 0) {
208                  let skip = DOM.tag('span', 'skip');
209                  let skipStr = '';
210                  for (let j = lineStart; j > 0; --j)
211                      skipStr += '   ';
212                  if (lineStart >= 8)
213                      skipStr += ' ';
214                  skip.innerText = skipStr;
215                  node.appendChild(skip);
216              }
217          }
218          this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen());
219          this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent());
220          if (this.sub === null) {
221              let start = this.posContent();
222              let end = this.posEnd();
223              if (!trim || end - start < 10 * 16)
224                  node.appendChild(DOM.text(
225                      this.stream.hexDump(start, end)));
226              else {
227                  let end1 = start + 5 * 16 - (start & 0xF);
228                  let start2 = end - 16 - (end & 0xF);
229                  node.appendChild(DOM.text(this.stream.hexDump(start, end1)));
230                  node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n'));
231                  node.appendChild(DOM.text(this.stream.hexDump(start2, end)));
232              }
233          } else if (this.sub.length > 0) {
234              let first = this.sub[0];
235              let last = this.sub[this.sub.length - 1];
236              this.toHexDOM_sub(node, 'intro', this.stream, this.posContent(), first.posStart());
237              for (let i = 0, max = this.sub.length; i < max; ++i)
238                  node.appendChild(this.sub[i].toHexDOM(root, trim));
239              this.toHexDOM_sub(node, 'outro', this.stream, last.posEnd(), this.posEnd());
240          } else
241              this.toHexDOM_sub(node, 'outro', this.stream, this.posContent(), this.posEnd());
242          return node;
243      }
244      static decode(stream, offset) {
245          return ASN1.decode(stream, offset, ASN1DOM);
246      }
247  
248  }