tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

compressor.js (18286B)


      1 var expect = require('chai').expect;
      2 var util = require('./util');
      3 
      4 var compressor = require('../lib/protocol/compressor');
      5 var HeaderTable = compressor.HeaderTable;
      6 var HuffmanTable = compressor.HuffmanTable;
      7 var HeaderSetCompressor = compressor.HeaderSetCompressor;
      8 var HeaderSetDecompressor = compressor.HeaderSetDecompressor;
      9 var Compressor = compressor.Compressor;
     10 var Decompressor = compressor.Decompressor;
     11 
     12 var test_integers = [{
     13  N: 5,
     14  I: 10,
     15  buffer: Buffer.from([10])
     16 }, {
     17  N: 0,
     18  I: 10,
     19  buffer: Buffer.from([10])
     20 }, {
     21  N: 5,
     22  I: 1337,
     23  buffer: Buffer.from([31, 128 + 26, 10])
     24 }, {
     25  N: 0,
     26  I: 1337,
     27  buffer: Buffer.from([128 + 57, 10])
     28 }];
     29 
     30 var test_strings = [{
     31  string: 'www.foo.com',
     32  buffer: Buffer.from('89f1e3c2f29ceb90f4ff', 'hex')
     33 }, {
     34  string: 'éáűőúöüó€',
     35  buffer: Buffer.from('13c3a9c3a1c5b1c591c3bac3b6c3bcc3b3e282ac', 'hex')
     36 }];
     37 
     38 test_huffman_request = {
     39  'GET': 'c5837f',
     40  'http': '9d29af',
     41  '/': '63',
     42  'www.foo.com': 'f1e3c2f29ceb90f4ff',
     43  'https': '9d29ad1f',
     44  'www.bar.com': 'f1e3c2f18ec5c87a7f',
     45  'no-cache': 'a8eb10649cbf',
     46  '/custom-path.css': '6096a127a56ac699d72211',
     47  'custom-key': '25a849e95ba97d7f',
     48  'custom-value': '25a849e95bb8e8b4bf'
     49 };
     50 
     51 test_huffman_response = {
     52  '302': '6402',
     53  'private': 'aec3771a4b',
     54  'Mon, 21 OCt 2013 20:13:21 GMT': 'd07abe941054d5792a0801654102e059b820a98b46ff',
     55  ': https://www.bar.com': 'b8a4e94d68b8c31e3c785e31d8b90f4f',
     56  '200': '1001',
     57  'Mon, 21 OCt 2013 20:13:22 GMT': 'd07abe941054d5792a0801654102e059b821298b46ff',
     58  'https://www.bar.com': '9d29ad171863c78f0bc63b1721e9',
     59  'gzip': '9bd9ab',
     60  'foo=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
     61 AAAAAAAAAAAAAAAAAAAAAAAAAALASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\
     62 QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\
     63 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\
     64 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\
     65 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234ZZZZZZZZZZ\
     66 ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1234 m\
     67 ax-age=3600; version=1': '94e7821861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861873c3bafe5cd8f666bbfbf9ab672c1ab5e4e10fe6ce583564e10fe67cb9b1ece5ab064e10e7d9cb06ac9c21fccfb307087f33e7cd961dd7f672c1ab86487f34844cb59e1dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab27087f33e5cd8f672d583270873ece583564e10fe67d983843f99f3e6cb0eebfb3960d5c3243f9a42265acf0eebf97363d99aefefe6ad9cb06ad793843f9b3960d593843f99f2e6c7b396ac1938439f672c1ab27087f33ecc1c21fccf9f3658775fd9cb06ae1921fcd21132d678775fcb9b1eccd77f7f356ce58356bc9c21fcd9cb06ac9c21fccf97363d9cb560c9c21cfb3960d593843f99f660e10fe67cf9b2c3bafece583570c90fe6908996bf7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f42265a5291f9587316065c003ed4ee5b1063d5007f',
     68  'foo=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\
     69 ZZZZZZZZZZZZZZZZZZZZZZZZZZLASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\
     70 QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\
     71 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\
     72 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\
     73 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234AAAAAAAAAA\
     74 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 m\
     75 ax-age=3600; version=1': '94e783f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f73c3bafe5cd8f666bbfbf9ab672c1ab5e4e10fe6ce583564e10fe67cb9b1ece5ab064e10e7d9cb06ac9c21fccfb307087f33e7cd961dd7f672c1ab86487f34844cb59e1dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab27087f33e5cd8f672d583270873ece583564e10fe67d983843f99f3e6cb0eebfb3960d5c3243f9a42265acf0eebf97363d99aefefe6ad9cb06ad793843f9b3960d593843f99f2e6c7b396ac1938439f672c1ab27087f33ecc1c21fccf9f3658775fd9cb06ae1921fcd21132d678775fcb9b1eccd77f7f356ce58356bc9c21fcd9cb06ac9c21fccf97363d9cb560c9c21cfb3960d593843f99f660e10fe67cf9b2c3bafece583570c90fe6908996a1861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861842265a5291f9587316065c003ed4ee5b1063d5007f'
     76 };
     77 
     78 var test_headers = [{
     79  // index
     80  header: {
     81    name: 1,
     82    value: 1,
     83    index: false,
     84    mustNeverIndex: false,
     85    contextUpdate: false,
     86    newMaxSize: 0
     87  },
     88  buffer: Buffer.from('82', 'hex')
     89 }, {
     90  // index
     91  header: {
     92    name: 5,
     93    value: 5,
     94    index: false,
     95    mustNeverIndex: false,
     96    contextUpdate: false,
     97    newMaxSize: 0
     98  },
     99  buffer: Buffer.from('86', 'hex')
    100 }, {
    101  // index
    102  header: {
    103    name: 3,
    104    value: 3,
    105    index: false,
    106    mustNeverIndex: false,
    107    contextUpdate: false,
    108    newMaxSize: 0
    109  },
    110  buffer: Buffer.from('84', 'hex')
    111 }, {
    112  // literal w/index, name index
    113  header: {
    114    name: 0,
    115    value: 'www.foo.com',
    116    index: true,
    117    mustNeverIndex: false,
    118    contextUpdate: false,
    119    newMaxSize: 0
    120  },
    121  buffer: Buffer.from('41' + '89f1e3c2f29ceb90f4ff', 'hex')
    122 }, {
    123  // indexed
    124  header: {
    125    name: 1,
    126    value: 1,
    127    index: false,
    128    mustNeverIndex: false,
    129    contextUpdate: false,
    130    newMaxSize: 0
    131  },
    132  buffer: Buffer.from('82', 'hex')
    133 }, {
    134  // indexed
    135  header: {
    136    name: 6,
    137    value: 6,
    138    index: false,
    139    mustNeverIndex: false,
    140    contextUpdate: false,
    141    newMaxSize: 0
    142  },
    143  buffer: Buffer.from('87', 'hex')
    144 }, {
    145  // indexed
    146  header: {
    147    name: 3,
    148    value: 3,
    149    index: false,
    150    mustNeverIndex: false,
    151    contextUpdate: false,
    152    newMaxSize: 0
    153  },
    154  buffer: Buffer.from('84', 'hex')
    155 }, {
    156  // literal w/index, name index
    157  header: {
    158    name: 0,
    159    value: 'www.bar.com',
    160    index: true,
    161    mustNeverIndex: false,
    162    contextUpdate: false,
    163    newMaxSize: 0
    164  },
    165  buffer: Buffer.from('41' + '89f1e3c2f18ec5c87a7f', 'hex')
    166 }, {
    167  // literal w/index, name index
    168  header: {
    169    name: 23,
    170    value: 'no-cache',
    171    index: true,
    172    mustNeverIndex: false,
    173    contextUpdate: false,
    174    newMaxSize: 0
    175  },
    176  buffer: Buffer.from('58' + '86a8eb10649cbf', 'hex')
    177 }, {
    178  // index
    179  header: {
    180    name: 1,
    181    value: 1,
    182    index: false,
    183    mustNeverIndex: false,
    184    contextUpdate: false,
    185    newMaxSize: 0
    186  },
    187  buffer: Buffer.from('82', 'hex')
    188 }, {
    189  // index
    190  header: {
    191    name: 6,
    192    value: 6,
    193    index: false,
    194    mustNeverIndex: false,
    195    contextUpdate: false,
    196    newMaxSize: 0
    197  },
    198  buffer: Buffer.from('87', 'hex')
    199 }, {
    200  // literal w/index, name index
    201  header: {
    202    name: 3,
    203    value: '/custom-path.css',
    204    index: true,
    205    mustNeverIndex: false,
    206    contextUpdate: false,
    207    newMaxSize: 0
    208  },
    209  buffer: Buffer.from('44' + '8b6096a127a56ac699d72211', 'hex')
    210 }, {
    211  // index
    212  header: {
    213    name: 63,
    214    value: 63,
    215    index: false,
    216    mustNeverIndex: false,
    217    contextUpdate: false,
    218    newMaxSize: 0
    219  },
    220  buffer: Buffer.from('C0', 'hex')
    221 }, {
    222  // literal w/index, new name & value
    223  header: {
    224    name: 'custom-key',
    225    value: 'custom-value',
    226    index: true,
    227    mustNeverIndex: false,
    228    contextUpdate: false,
    229    newMaxSize: 0
    230  },
    231  buffer: Buffer.from('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex')
    232 }, {
    233  // index
    234  header: {
    235    name: 1,
    236    value: 1,
    237    index: false,
    238    mustNeverIndex: false,
    239    contextUpdate: false,
    240    newMaxSize: 0
    241  },
    242  buffer: Buffer.from('82', 'hex')
    243 }, {
    244  // index
    245  header: {
    246    name: 6,
    247    value: 6,
    248    index: false,
    249    mustNeverIndex: false,
    250    contextUpdate: false,
    251    newMaxSize: 0
    252  },
    253  buffer: Buffer.from('87', 'hex')
    254 }, {
    255  // index
    256  header: {
    257    name: 62,
    258    value: 62,
    259    index: false,
    260    mustNeverIndex: false,
    261    contextUpdate: false,
    262    newMaxSize: 0
    263  },
    264  buffer: Buffer.from('BF', 'hex')
    265 }, {
    266  // index
    267  header: {
    268    name: 65,
    269    value: 65,
    270    index: false,
    271    mustNeverIndex: false,
    272    contextUpdate: false,
    273    newMaxSize: 0
    274  },
    275  buffer: Buffer.from('C2', 'hex')
    276 }, {
    277  // index
    278  header: {
    279    name: 64,
    280    value: 64,
    281    index: false,
    282    mustNeverIndex: false,
    283    contextUpdate: false,
    284    newMaxSize: 0
    285  },
    286  buffer: Buffer.from('C1', 'hex')
    287 }, {
    288  // index
    289  header: {
    290    name: 61,
    291    value: 61,
    292    index: false,
    293    mustNeverIndex: false,
    294    contextUpdate: false,
    295    newMaxSize: 0
    296  },
    297  buffer: Buffer.from('BE', 'hex')
    298 }, {
    299  // Literal w/o index, name index
    300  header: {
    301    name: 6,
    302    value: "whatever",
    303    index: false,
    304    mustNeverIndex: false,
    305    contextUpdate: false,
    306    newMaxSize: 0
    307  },
    308  buffer: Buffer.from('07' + '86f138d25ee5b3', 'hex')
    309 }, {
    310  // Literal w/o index, new name & value
    311  header: {
    312    name: "foo",
    313    value: "bar",
    314    index: false,
    315    mustNeverIndex: false,
    316    contextUpdate: false,
    317    newMaxSize: 0
    318  },
    319  buffer: Buffer.from('00' + '8294e7' + '03626172', 'hex')
    320 }, {
    321  // Literal never indexed, name index
    322  header: {
    323    name: 6,
    324    value: "whatever",
    325    index: false,
    326    mustNeverIndex: true,
    327    contextUpdate: false,
    328    newMaxSize: 0
    329  },
    330  buffer: Buffer.from('17' + '86f138d25ee5b3', 'hex')
    331 }, {
    332  // Literal never indexed, new name & value
    333  header: {
    334    name: "foo",
    335    value: "bar",
    336    index: false,
    337    mustNeverIndex: true,
    338    contextUpdate: false,
    339    newMaxSize: 0
    340  },
    341  buffer: Buffer.from('10' + '8294e7' + '03626172', 'hex')
    342 }, {
    343  header: {
    344    name: -1,
    345    value: -1,
    346    index: false,
    347    mustNeverIndex: false,
    348    contextUpdate: true,
    349    newMaxSize: 100
    350  },
    351  buffer: Buffer.from('3F45', 'hex')
    352 }];
    353 
    354 var test_header_sets = [{
    355  headers: {
    356    ':method': 'GET',
    357    ':scheme': 'http',
    358    ':path': '/',
    359    ':authority': 'www.foo.com'
    360  },
    361  buffer: util.concat(test_headers.slice(0, 4).map(function(test) { return test.buffer; }))
    362 }, {
    363  headers: {
    364    ':method': 'GET',
    365    ':scheme': 'https',
    366    ':path': '/',
    367    ':authority': 'www.bar.com',
    368    'cache-control': 'no-cache'
    369  },
    370  buffer: util.concat(test_headers.slice(4, 9).map(function(test) { return test.buffer; }))
    371 }, {
    372  headers: {
    373    ':method': 'GET',
    374    ':scheme': 'https',
    375    ':path': '/custom-path.css',
    376    ':authority': 'www.bar.com',
    377    'custom-key': 'custom-value'
    378  },
    379  buffer: util.concat(test_headers.slice(9, 14).map(function(test) { return test.buffer; }))
    380 }, {
    381  headers: {
    382    ':method': 'GET',
    383    ':scheme': 'https',
    384    ':path': '/custom-path.css',
    385    ':authority': ['www.foo.com', 'www.bar.com'],
    386    'custom-key': 'custom-value'
    387  },
    388  buffer: util.concat(test_headers.slice(14, 19).map(function(test) { return test.buffer; }))
    389 }];
    390 
    391 describe('compressor.js', function() {
    392  describe('HeaderTable', function() {
    393  });
    394 
    395  describe('HuffmanTable', function() {
    396    describe('method encode(buffer)', function() {
    397      it('should return the Huffman encoded version of the input buffer', function() {
    398        var table = HuffmanTable.huffmanTable;
    399        for (var decoded in test_huffman_request) {
    400          var encoded = test_huffman_request[decoded];
    401          expect(table.encode(Buffer.from(decoded)).toString('hex')).to.equal(encoded);
    402        }
    403        table = HuffmanTable.huffmanTable;
    404        for (decoded in test_huffman_response) {
    405          encoded = test_huffman_response[decoded];
    406          expect(table.encode(Buffer.from(decoded)).toString('hex')).to.equal(encoded);
    407        }
    408      });
    409    });
    410    describe('method decode(buffer)', function() {
    411      it('should return the Huffman decoded version of the input buffer', function() {
    412        var table = HuffmanTable.huffmanTable;
    413        for (var decoded in test_huffman_request) {
    414          var encoded = test_huffman_request[decoded];
    415          expect(table.decode(Buffer.from(encoded, 'hex')).toString()).to.equal(decoded);
    416        }
    417        table = HuffmanTable.huffmanTable;
    418        for (decoded in test_huffman_response) {
    419          encoded = test_huffman_response[decoded];
    420          expect(table.decode(Buffer.from(encoded, 'hex')).toString()).to.equal(decoded);
    421        }
    422      });
    423    });
    424  });
    425 
    426  describe('HeaderSetCompressor', function() {
    427    describe('static method .integer(I, N)', function() {
    428      it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() {
    429        for (var i = 0; i < test_integers.length; i++) {
    430          var test = test_integers[i];
    431          test.buffer.cursor = 0;
    432          expect(util.concat(HeaderSetCompressor.integer(test.I, test.N))).to.deep.equal(test.buffer);
    433        }
    434      });
    435    });
    436    describe('static method .string(string)', function() {
    437      it('should return an array of buffers that represent the encoded form of the string', function() {
    438        var table = HuffmanTable.huffmanTable;
    439        for (var i = 0; i < test_strings.length; i++) {
    440          var test = test_strings[i];
    441          expect(util.concat(HeaderSetCompressor.string(test.string, table))).to.deep.equal(test.buffer);
    442        }
    443      });
    444    });
    445    describe('static method .header({ name, value, index })', function() {
    446      it('should return an array of buffers that represent the encoded form of the header', function() {
    447        var table = HuffmanTable.huffmanTable;
    448        for (var i = 0; i < test_headers.length; i++) {
    449          var test = test_headers[i];
    450          expect(util.concat(HeaderSetCompressor.header(test.header, table))).to.deep.equal(test.buffer);
    451        }
    452      });
    453    });
    454  });
    455 
    456  describe('HeaderSetDecompressor', function() {
    457    describe('static method .integer(buffer, N)', function() {
    458      it('should return the parsed N-prefix coded number and increase the cursor property of buffer', function() {
    459        for (var i = 0; i < test_integers.length; i++) {
    460          var test = test_integers[i];
    461          test.buffer.cursor = 0;
    462          expect(HeaderSetDecompressor.integer(test.buffer, test.N)).to.equal(test.I);
    463          expect(test.buffer.cursor).to.equal(test.buffer.length);
    464        }
    465      });
    466    });
    467    describe('static method .string(buffer)', function() {
    468      it('should return the parsed string and increase the cursor property of buffer', function() {
    469        var table = HuffmanTable.huffmanTable;
    470        for (var i = 0; i < test_strings.length; i++) {
    471          var test = test_strings[i];
    472          test.buffer.cursor = 0;
    473          expect(HeaderSetDecompressor.string(test.buffer, table)).to.equal(test.string);
    474          expect(test.buffer.cursor).to.equal(test.buffer.length);
    475        }
    476      });
    477    });
    478    describe('static method .header(buffer)', function() {
    479      it('should return the parsed header and increase the cursor property of buffer', function() {
    480        var table = HuffmanTable.huffmanTable;
    481        for (var i = 0; i < test_headers.length; i++) {
    482          var test = test_headers[i];
    483          test.buffer.cursor = 0;
    484          expect(HeaderSetDecompressor.header(test.buffer, table)).to.deep.equal(test.header);
    485          expect(test.buffer.cursor).to.equal(test.buffer.length);
    486        }
    487      });
    488    });
    489  });
    490  describe('Decompressor', function() {
    491    describe('method decompress(buffer)', function() {
    492      it('should return the parsed header set in { name1: value1, name2: [value2, value3], ... } format', function() {
    493        var decompressor = new Decompressor(util.log, 'REQUEST');
    494        for (var i = 0; i < test_header_sets.length - 1; i++) {
    495          var header_set = test_header_sets[i];
    496          expect(decompressor.decompress(header_set.buffer)).to.deep.equal(header_set.headers);
    497        }
    498      });
    499    });
    500    describe('transform stream', function() {
    501      it('should emit an error event if a series of header frames is interleaved with other frames', function() {
    502        var decompressor = new Decompressor(util.log, 'REQUEST');
    503        var error_occured = false;
    504        decompressor.on('error', function() {
    505          error_occured = true;
    506        });
    507        decompressor.write({
    508          type: 'HEADERS',
    509          flags: {
    510            END_HEADERS: false
    511          },
    512          data: Buffer.alloc(5)
    513        });
    514        decompressor.write({
    515          type: 'DATA',
    516          flags: {},
    517          data: Buffer.alloc(5)
    518        });
    519        expect(error_occured).to.be.equal(true);
    520      });
    521    });
    522  });
    523 
    524  describe('invariant', function() {
    525    describe('decompressor.decompress(compressor.compress(headerset)) === headerset', function() {
    526      it('should be true for any header set if the states are synchronized', function() {
    527        var compressor = new Compressor(util.log, 'REQUEST');
    528        var decompressor = new Decompressor(util.log, 'REQUEST');
    529        var n = test_header_sets.length;
    530        for (var i = 0; i < 10; i++) {
    531          var headers = test_header_sets[i%n].headers;
    532          var compressed = compressor.compress(headers);
    533          var decompressed = decompressor.decompress(compressed);
    534          expect(decompressed).to.deep.equal(headers);
    535          expect(compressor._table).to.deep.equal(decompressor._table);
    536        }
    537      });
    538    });
    539    describe('source.pipe(compressor).pipe(decompressor).pipe(destination)', function() {
    540      it('should behave like source.pipe(destination) for a stream of frames', function(done) {
    541        var compressor = new Compressor(util.log, 'RESPONSE');
    542        var decompressor = new Decompressor(util.log, 'RESPONSE');
    543        var n = test_header_sets.length;
    544        compressor.pipe(decompressor);
    545        for (var i = 0; i < 10; i++) {
    546          compressor.write({
    547            type: i%2 ? 'HEADERS' : 'PUSH_PROMISE',
    548            flags: {},
    549            headers: test_header_sets[i%n].headers
    550          });
    551        }
    552        setTimeout(function() {
    553          for (var j = 0; j < 10; j++) {
    554            expect(decompressor.read().headers).to.deep.equal(test_header_sets[j%n].headers);
    555          }
    556          done();
    557        }, 10);
    558      });
    559    });
    560    describe('huffmanTable.decompress(huffmanTable.compress(buffer)) === buffer', function() {
    561      it('should be true for any buffer', function() {
    562        for (var i = 0; i < 10; i++) {
    563          var buffer = [];
    564          while (Math.random() > 0.1) {
    565            buffer.push(Math.floor(Math.random() * 256))
    566          }
    567          buffer = Buffer.from(buffer);
    568          var table = HuffmanTable.huffmanTable;
    569          var result = table.decode(table.encode(buffer));
    570          expect(result).to.deep.equal(buffer);
    571        }
    572      });
    573    });
    574  });
    575 });