twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

reader.js (5650B)


      1 // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
      2 
      3 var assert = require('assert');
      4 var Buffer = require('safer-buffer').Buffer;
      5 
      6 var ASN1 = require('./types');
      7 var errors = require('./errors');
      8 
      9 
     10 // --- Globals
     11 
     12 var newInvalidAsn1Error = errors.newInvalidAsn1Error;
     13 
     14 
     15 
     16 // --- API
     17 
     18 function Reader(data) {
     19   if (!data || !Buffer.isBuffer(data))
     20     throw new TypeError('data must be a node Buffer');
     21 
     22   this._buf = data;
     23   this._size = data.length;
     24 
     25   // These hold the "current" state
     26   this._len = 0;
     27   this._offset = 0;
     28 }
     29 
     30 Object.defineProperty(Reader.prototype, 'length', {
     31   enumerable: true,
     32   get: function () { return (this._len); }
     33 });
     34 
     35 Object.defineProperty(Reader.prototype, 'offset', {
     36   enumerable: true,
     37   get: function () { return (this._offset); }
     38 });
     39 
     40 Object.defineProperty(Reader.prototype, 'remain', {
     41   get: function () { return (this._size - this._offset); }
     42 });
     43 
     44 Object.defineProperty(Reader.prototype, 'buffer', {
     45   get: function () { return (this._buf.slice(this._offset)); }
     46 });
     47 
     48 
     49 /**
     50  * Reads a single byte and advances offset; you can pass in `true` to make this
     51  * a "peek" operation (i.e., get the byte, but don't advance the offset).
     52  *
     53  * @param {Boolean} peek true means don't move offset.
     54  * @return {Number} the next byte, null if not enough data.
     55  */
     56 Reader.prototype.readByte = function (peek) {
     57   if (this._size - this._offset < 1)
     58     return null;
     59 
     60   var b = this._buf[this._offset] & 0xff;
     61 
     62   if (!peek)
     63     this._offset += 1;
     64 
     65   return b;
     66 };
     67 
     68 
     69 Reader.prototype.peek = function () {
     70   return this.readByte(true);
     71 };
     72 
     73 
     74 /**
     75  * Reads a (potentially) variable length off the BER buffer.  This call is
     76  * not really meant to be called directly, as callers have to manipulate
     77  * the internal buffer afterwards.
     78  *
     79  * As a result of this call, you can call `Reader.length`, until the
     80  * next thing called that does a readLength.
     81  *
     82  * @return {Number} the amount of offset to advance the buffer.
     83  * @throws {InvalidAsn1Error} on bad ASN.1
     84  */
     85 Reader.prototype.readLength = function (offset) {
     86   if (offset === undefined)
     87     offset = this._offset;
     88 
     89   if (offset >= this._size)
     90     return null;
     91 
     92   var lenB = this._buf[offset++] & 0xff;
     93   if (lenB === null)
     94     return null;
     95 
     96   if ((lenB & 0x80) === 0x80) {
     97     lenB &= 0x7f;
     98 
     99     if (lenB === 0)
    100       throw newInvalidAsn1Error('Indefinite length not supported');
    101 
    102     if (lenB > 4)
    103       throw newInvalidAsn1Error('encoding too long');
    104 
    105     if (this._size - offset < lenB)
    106       return null;
    107 
    108     this._len = 0;
    109     for (var i = 0; i < lenB; i++)
    110       this._len = (this._len << 8) + (this._buf[offset++] & 0xff);
    111 
    112   } else {
    113     // Wasn't a variable length
    114     this._len = lenB;
    115   }
    116 
    117   return offset;
    118 };
    119 
    120 
    121 /**
    122  * Parses the next sequence in this BER buffer.
    123  *
    124  * To get the length of the sequence, call `Reader.length`.
    125  *
    126  * @return {Number} the sequence's tag.
    127  */
    128 Reader.prototype.readSequence = function (tag) {
    129   var seq = this.peek();
    130   if (seq === null)
    131     return null;
    132   if (tag !== undefined && tag !== seq)
    133     throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
    134                               ': got 0x' + seq.toString(16));
    135 
    136   var o = this.readLength(this._offset + 1); // stored in `length`
    137   if (o === null)
    138     return null;
    139 
    140   this._offset = o;
    141   return seq;
    142 };
    143 
    144 
    145 Reader.prototype.readInt = function () {
    146   return this._readTag(ASN1.Integer);
    147 };
    148 
    149 
    150 Reader.prototype.readBoolean = function () {
    151   return (this._readTag(ASN1.Boolean) === 0 ? false : true);
    152 };
    153 
    154 
    155 Reader.prototype.readEnumeration = function () {
    156   return this._readTag(ASN1.Enumeration);
    157 };
    158 
    159 
    160 Reader.prototype.readString = function (tag, retbuf) {
    161   if (!tag)
    162     tag = ASN1.OctetString;
    163 
    164   var b = this.peek();
    165   if (b === null)
    166     return null;
    167 
    168   if (b !== tag)
    169     throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
    170                               ': got 0x' + b.toString(16));
    171 
    172   var o = this.readLength(this._offset + 1); // stored in `length`
    173 
    174   if (o === null)
    175     return null;
    176 
    177   if (this.length > this._size - o)
    178     return null;
    179 
    180   this._offset = o;
    181 
    182   if (this.length === 0)
    183     return retbuf ? Buffer.alloc(0) : '';
    184 
    185   var str = this._buf.slice(this._offset, this._offset + this.length);
    186   this._offset += this.length;
    187 
    188   return retbuf ? str : str.toString('utf8');
    189 };
    190 
    191 Reader.prototype.readOID = function (tag) {
    192   if (!tag)
    193     tag = ASN1.OID;
    194 
    195   var b = this.readString(tag, true);
    196   if (b === null)
    197     return null;
    198 
    199   var values = [];
    200   var value = 0;
    201 
    202   for (var i = 0; i < b.length; i++) {
    203     var byte = b[i] & 0xff;
    204 
    205     value <<= 7;
    206     value += byte & 0x7f;
    207     if ((byte & 0x80) === 0) {
    208       values.push(value);
    209       value = 0;
    210     }
    211   }
    212 
    213   value = values.shift();
    214   values.unshift(value % 40);
    215   values.unshift((value / 40) >> 0);
    216 
    217   return values.join('.');
    218 };
    219 
    220 
    221 Reader.prototype._readTag = function (tag) {
    222   assert.ok(tag !== undefined);
    223 
    224   var b = this.peek();
    225 
    226   if (b === null)
    227     return null;
    228 
    229   if (b !== tag)
    230     throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
    231                               ': got 0x' + b.toString(16));
    232 
    233   var o = this.readLength(this._offset + 1); // stored in `length`
    234   if (o === null)
    235     return null;
    236 
    237   if (this.length > 4)
    238     throw newInvalidAsn1Error('Integer too long: ' + this.length);
    239 
    240   if (this.length > this._size - o)
    241     return null;
    242   this._offset = o;
    243 
    244   var fb = this._buf[this._offset];
    245   var value = 0;
    246 
    247   for (var i = 0; i < this.length; i++) {
    248     value <<= 8;
    249     value |= (this._buf[this._offset++] & 0xff);
    250   }
    251 
    252   if ((fb & 0x80) === 0x80 && i !== 4)
    253     value -= (1 << (i * 8));
    254 
    255   return value >> 0;
    256 };
    257 
    258 
    259 
    260 // --- Exported API
    261 
    262 module.exports = Reader;