permessage-deflate.test.js (21871B)
1 'use strict'; 2 3 const assert = require('assert'); 4 5 const PerMessageDeflate = require('../lib/permessage-deflate'); 6 const extension = require('../lib/extension'); 7 8 describe('PerMessageDeflate', () => { 9 describe('#offer', () => { 10 it('creates an offer', () => { 11 const perMessageDeflate = new PerMessageDeflate(); 12 13 assert.deepStrictEqual(perMessageDeflate.offer(), { 14 client_max_window_bits: true 15 }); 16 }); 17 18 it('uses the configuration options', () => { 19 const perMessageDeflate = new PerMessageDeflate({ 20 serverNoContextTakeover: true, 21 clientNoContextTakeover: true, 22 serverMaxWindowBits: 10, 23 clientMaxWindowBits: 11 24 }); 25 26 assert.deepStrictEqual(perMessageDeflate.offer(), { 27 server_no_context_takeover: true, 28 client_no_context_takeover: true, 29 server_max_window_bits: 10, 30 client_max_window_bits: 11 31 }); 32 }); 33 }); 34 35 describe('#accept', () => { 36 it('throws an error if a parameter has multiple values', () => { 37 const perMessageDeflate = new PerMessageDeflate(); 38 const extensions = extension.parse( 39 'permessage-deflate; server_no_context_takeover; server_no_context_takeover' 40 ); 41 42 assert.throws( 43 () => perMessageDeflate.accept(extensions['permessage-deflate']), 44 /^Error: Parameter "server_no_context_takeover" must have only a single value$/ 45 ); 46 }); 47 48 it('throws an error if a parameter has an invalid name', () => { 49 const perMessageDeflate = new PerMessageDeflate(); 50 const extensions = extension.parse('permessage-deflate;foo'); 51 52 assert.throws( 53 () => perMessageDeflate.accept(extensions['permessage-deflate']), 54 /^Error: Unknown parameter "foo"$/ 55 ); 56 }); 57 58 it('throws an error if client_no_context_takeover has a value', () => { 59 const perMessageDeflate = new PerMessageDeflate(); 60 const extensions = extension.parse( 61 'permessage-deflate; client_no_context_takeover=10' 62 ); 63 64 assert.throws( 65 () => perMessageDeflate.accept(extensions['permessage-deflate']), 66 /^TypeError: Invalid value for parameter "client_no_context_takeover": 10$/ 67 ); 68 }); 69 70 it('throws an error if server_no_context_takeover has a value', () => { 71 const perMessageDeflate = new PerMessageDeflate(); 72 const extensions = extension.parse( 73 'permessage-deflate; server_no_context_takeover=10' 74 ); 75 76 assert.throws( 77 () => perMessageDeflate.accept(extensions['permessage-deflate']), 78 /^TypeError: Invalid value for parameter "server_no_context_takeover": 10$/ 79 ); 80 }); 81 82 it('throws an error if server_max_window_bits has an invalid value', () => { 83 const perMessageDeflate = new PerMessageDeflate(); 84 85 let extensions = extension.parse( 86 'permessage-deflate; server_max_window_bits=7' 87 ); 88 assert.throws( 89 () => perMessageDeflate.accept(extensions['permessage-deflate']), 90 /^TypeError: Invalid value for parameter "server_max_window_bits": 7$/ 91 ); 92 93 extensions = extension.parse( 94 'permessage-deflate; server_max_window_bits' 95 ); 96 assert.throws( 97 () => perMessageDeflate.accept(extensions['permessage-deflate']), 98 /^TypeError: Invalid value for parameter "server_max_window_bits": true$/ 99 ); 100 }); 101 102 describe('As server', () => { 103 it('accepts an offer with no parameters', () => { 104 const perMessageDeflate = new PerMessageDeflate({}, true); 105 106 assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); 107 }); 108 109 it('accepts an offer with parameters', () => { 110 const perMessageDeflate = new PerMessageDeflate({}, true); 111 const extensions = extension.parse( 112 'permessage-deflate; server_no_context_takeover; ' + 113 'client_no_context_takeover; server_max_window_bits=10; ' + 114 'client_max_window_bits=11' 115 ); 116 117 assert.deepStrictEqual( 118 perMessageDeflate.accept(extensions['permessage-deflate']), 119 { 120 server_no_context_takeover: true, 121 client_no_context_takeover: true, 122 server_max_window_bits: 10, 123 client_max_window_bits: 11, 124 __proto__: null 125 } 126 ); 127 }); 128 129 it('prefers the configuration options', () => { 130 const perMessageDeflate = new PerMessageDeflate( 131 { 132 serverNoContextTakeover: true, 133 clientNoContextTakeover: true, 134 serverMaxWindowBits: 12, 135 clientMaxWindowBits: 11 136 }, 137 true 138 ); 139 const extensions = extension.parse( 140 'permessage-deflate; server_max_window_bits=14; client_max_window_bits=13' 141 ); 142 143 assert.deepStrictEqual( 144 perMessageDeflate.accept(extensions['permessage-deflate']), 145 { 146 server_no_context_takeover: true, 147 client_no_context_takeover: true, 148 server_max_window_bits: 12, 149 client_max_window_bits: 11, 150 __proto__: null 151 } 152 ); 153 }); 154 155 it('accepts the first supported offer', () => { 156 const perMessageDeflate = new PerMessageDeflate( 157 { serverMaxWindowBits: 11 }, 158 true 159 ); 160 const extensions = extension.parse( 161 'permessage-deflate; server_max_window_bits=10, permessage-deflate' 162 ); 163 164 assert.deepStrictEqual( 165 perMessageDeflate.accept(extensions['permessage-deflate']), 166 { 167 server_max_window_bits: 11, 168 __proto__: null 169 } 170 ); 171 }); 172 173 it('throws an error if server_no_context_takeover is unsupported', () => { 174 const perMessageDeflate = new PerMessageDeflate( 175 { serverNoContextTakeover: false }, 176 true 177 ); 178 const extensions = extension.parse( 179 'permessage-deflate; server_no_context_takeover' 180 ); 181 182 assert.throws( 183 () => perMessageDeflate.accept(extensions['permessage-deflate']), 184 /^Error: None of the extension offers can be accepted$/ 185 ); 186 }); 187 188 it('throws an error if server_max_window_bits is unsupported', () => { 189 const perMessageDeflate = new PerMessageDeflate( 190 { serverMaxWindowBits: false }, 191 true 192 ); 193 const extensions = extension.parse( 194 'permessage-deflate; server_max_window_bits=10' 195 ); 196 197 assert.throws( 198 () => perMessageDeflate.accept(extensions['permessage-deflate']), 199 /^Error: None of the extension offers can be accepted$/ 200 ); 201 }); 202 203 it('throws an error if server_max_window_bits is less than configuration', () => { 204 const perMessageDeflate = new PerMessageDeflate( 205 { serverMaxWindowBits: 11 }, 206 true 207 ); 208 const extensions = extension.parse( 209 'permessage-deflate; server_max_window_bits=10' 210 ); 211 212 assert.throws( 213 () => perMessageDeflate.accept(extensions['permessage-deflate']), 214 /^Error: None of the extension offers can be accepted$/ 215 ); 216 }); 217 218 it('throws an error if client_max_window_bits is unsupported on client', () => { 219 const perMessageDeflate = new PerMessageDeflate( 220 { clientMaxWindowBits: 10 }, 221 true 222 ); 223 const extensions = extension.parse('permessage-deflate'); 224 225 assert.throws( 226 () => perMessageDeflate.accept(extensions['permessage-deflate']), 227 /^Error: None of the extension offers can be accepted$/ 228 ); 229 }); 230 231 it('throws an error if client_max_window_bits has an invalid value', () => { 232 const perMessageDeflate = new PerMessageDeflate({}, true); 233 234 const extensions = extension.parse( 235 'permessage-deflate; client_max_window_bits=16' 236 ); 237 assert.throws( 238 () => perMessageDeflate.accept(extensions['permessage-deflate']), 239 /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ 240 ); 241 }); 242 }); 243 244 describe('As client', () => { 245 it('accepts a response with no parameters', () => { 246 const perMessageDeflate = new PerMessageDeflate({}); 247 248 assert.deepStrictEqual(perMessageDeflate.accept([{}]), {}); 249 }); 250 251 it('accepts a response with parameters', () => { 252 const perMessageDeflate = new PerMessageDeflate({}); 253 const extensions = extension.parse( 254 'permessage-deflate; server_no_context_takeover; ' + 255 'client_no_context_takeover; server_max_window_bits=10; ' + 256 'client_max_window_bits=11' 257 ); 258 259 assert.deepStrictEqual( 260 perMessageDeflate.accept(extensions['permessage-deflate']), 261 { 262 server_no_context_takeover: true, 263 client_no_context_takeover: true, 264 server_max_window_bits: 10, 265 client_max_window_bits: 11, 266 __proto__: null 267 } 268 ); 269 }); 270 271 it('throws an error if client_no_context_takeover is unsupported', () => { 272 const perMessageDeflate = new PerMessageDeflate({ 273 clientNoContextTakeover: false 274 }); 275 const extensions = extension.parse( 276 'permessage-deflate; client_no_context_takeover' 277 ); 278 279 assert.throws( 280 () => perMessageDeflate.accept(extensions['permessage-deflate']), 281 /^Error: Unexpected parameter "client_no_context_takeover"$/ 282 ); 283 }); 284 285 it('throws an error if client_max_window_bits is unsupported', () => { 286 const perMessageDeflate = new PerMessageDeflate({ 287 clientMaxWindowBits: false 288 }); 289 const extensions = extension.parse( 290 'permessage-deflate; client_max_window_bits=10' 291 ); 292 293 assert.throws( 294 () => perMessageDeflate.accept(extensions['permessage-deflate']), 295 /^Error: Unexpected or invalid parameter "client_max_window_bits"$/ 296 ); 297 }); 298 299 it('throws an error if client_max_window_bits is greater than configuration', () => { 300 const perMessageDeflate = new PerMessageDeflate({ 301 clientMaxWindowBits: 10 302 }); 303 const extensions = extension.parse( 304 'permessage-deflate; client_max_window_bits=11' 305 ); 306 307 assert.throws( 308 () => perMessageDeflate.accept(extensions['permessage-deflate']), 309 /^Error: Unexpected or invalid parameter "client_max_window_bits"$/ 310 ); 311 }); 312 313 it('throws an error if client_max_window_bits has an invalid value', () => { 314 const perMessageDeflate = new PerMessageDeflate(); 315 316 let extensions = extension.parse( 317 'permessage-deflate; client_max_window_bits=16' 318 ); 319 assert.throws( 320 () => perMessageDeflate.accept(extensions['permessage-deflate']), 321 /^TypeError: Invalid value for parameter "client_max_window_bits": 16$/ 322 ); 323 324 extensions = extension.parse( 325 'permessage-deflate; client_max_window_bits' 326 ); 327 assert.throws( 328 () => perMessageDeflate.accept(extensions['permessage-deflate']), 329 /^TypeError: Invalid value for parameter "client_max_window_bits": true$/ 330 ); 331 }); 332 333 it('uses the config value if client_max_window_bits is not specified', () => { 334 const perMessageDeflate = new PerMessageDeflate({ 335 clientMaxWindowBits: 10 336 }); 337 338 assert.deepStrictEqual(perMessageDeflate.accept([{}]), { 339 client_max_window_bits: 10 340 }); 341 }); 342 }); 343 }); 344 345 describe('#compress and #decompress', () => { 346 it('works with unfragmented messages', (done) => { 347 const perMessageDeflate = new PerMessageDeflate(); 348 const buf = Buffer.from([1, 2, 3]); 349 350 perMessageDeflate.accept([{}]); 351 perMessageDeflate.compress(buf, true, (err, data) => { 352 if (err) return done(err); 353 354 perMessageDeflate.decompress(data, true, (err, data) => { 355 if (err) return done(err); 356 357 assert.ok(data.equals(buf)); 358 done(); 359 }); 360 }); 361 }); 362 363 it('works with fragmented messages', (done) => { 364 const perMessageDeflate = new PerMessageDeflate(); 365 const buf = Buffer.from([1, 2, 3, 4]); 366 367 perMessageDeflate.accept([{}]); 368 369 perMessageDeflate.compress(buf.slice(0, 2), false, (err, compressed1) => { 370 if (err) return done(err); 371 372 perMessageDeflate.compress(buf.slice(2), true, (err, compressed2) => { 373 if (err) return done(err); 374 375 perMessageDeflate.decompress(compressed1, false, (err, data1) => { 376 if (err) return done(err); 377 378 perMessageDeflate.decompress(compressed2, true, (err, data2) => { 379 if (err) return done(err); 380 381 assert.ok(Buffer.concat([data1, data2]).equals(buf)); 382 done(); 383 }); 384 }); 385 }); 386 }); 387 }); 388 389 it('works with the negotiated parameters', (done) => { 390 const perMessageDeflate = new PerMessageDeflate({ 391 memLevel: 5, 392 level: 9 393 }); 394 const extensions = extension.parse( 395 'permessage-deflate; server_no_context_takeover; ' + 396 'client_no_context_takeover; server_max_window_bits=10; ' + 397 'client_max_window_bits=11' 398 ); 399 const buf = Buffer.from("Some compressible data, it's compressible."); 400 401 perMessageDeflate.accept(extensions['permessage-deflate']); 402 403 perMessageDeflate.compress(buf, true, (err, data) => { 404 if (err) return done(err); 405 406 perMessageDeflate.decompress(data, true, (err, data) => { 407 if (err) return done(err); 408 409 assert.ok(data.equals(buf)); 410 done(); 411 }); 412 }); 413 }); 414 415 it('honors the `level` option', (done) => { 416 const lev0 = new PerMessageDeflate({ 417 zlibDeflateOptions: { level: 0 } 418 }); 419 const lev9 = new PerMessageDeflate({ 420 zlibDeflateOptions: { level: 9 } 421 }); 422 const extensionStr = 423 'permessage-deflate; server_no_context_takeover; ' + 424 'client_no_context_takeover; server_max_window_bits=10; ' + 425 'client_max_window_bits=11'; 426 const buf = Buffer.from("Some compressible data, it's compressible."); 427 428 lev0.accept(extension.parse(extensionStr)['permessage-deflate']); 429 lev9.accept(extension.parse(extensionStr)['permessage-deflate']); 430 431 lev0.compress(buf, true, (err, compressed1) => { 432 if (err) return done(err); 433 434 lev0.decompress(compressed1, true, (err, decompressed1) => { 435 if (err) return done(err); 436 437 lev9.compress(buf, true, (err, compressed2) => { 438 if (err) return done(err); 439 440 lev9.decompress(compressed2, true, (err, decompressed2) => { 441 if (err) return done(err); 442 443 // Level 0 compression actually adds a few bytes due to headers. 444 assert.ok(compressed1.length > buf.length); 445 // Level 9 should not, of course. 446 assert.ok(compressed2.length < buf.length); 447 // Ensure they both decompress back properly. 448 assert.ok(decompressed1.equals(buf)); 449 assert.ok(decompressed2.equals(buf)); 450 done(); 451 }); 452 }); 453 }); 454 }); 455 }); 456 457 it('honors the `zlib{Deflate,Inflate}Options` option', (done) => { 458 const lev0 = new PerMessageDeflate({ 459 zlibDeflateOptions: { 460 level: 0, 461 chunkSize: 256 462 }, 463 zlibInflateOptions: { 464 chunkSize: 2048 465 } 466 }); 467 const lev9 = new PerMessageDeflate({ 468 zlibDeflateOptions: { 469 level: 9, 470 chunkSize: 128 471 }, 472 zlibInflateOptions: { 473 chunkSize: 1024 474 } 475 }); 476 477 // Note no context takeover so we can get a hold of the raw streams after 478 // we do the dance. 479 const extensionStr = 480 'permessage-deflate; server_max_window_bits=10; ' + 481 'client_max_window_bits=11'; 482 const buf = Buffer.from("Some compressible data, it's compressible."); 483 484 lev0.accept(extension.parse(extensionStr)['permessage-deflate']); 485 lev9.accept(extension.parse(extensionStr)['permessage-deflate']); 486 487 lev0.compress(buf, true, (err, compressed1) => { 488 if (err) return done(err); 489 490 lev0.decompress(compressed1, true, (err, decompressed1) => { 491 if (err) return done(err); 492 493 lev9.compress(buf, true, (err, compressed2) => { 494 if (err) return done(err); 495 496 lev9.decompress(compressed2, true, (err, decompressed2) => { 497 if (err) return done(err); 498 // Level 0 compression actually adds a few bytes due to headers. 499 assert.ok(compressed1.length > buf.length); 500 // Level 9 should not, of course. 501 assert.ok(compressed2.length < buf.length); 502 // Ensure they both decompress back properly. 503 assert.ok(decompressed1.equals(buf)); 504 assert.ok(decompressed2.equals(buf)); 505 506 // Assert options were set. 507 assert.ok(lev0._deflate._level === 0); 508 assert.ok(lev9._deflate._level === 9); 509 assert.ok(lev0._deflate._chunkSize === 256); 510 assert.ok(lev9._deflate._chunkSize === 128); 511 assert.ok(lev0._inflate._chunkSize === 2048); 512 assert.ok(lev9._inflate._chunkSize === 1024); 513 done(); 514 }); 515 }); 516 }); 517 }); 518 }); 519 520 it("doesn't use contex takeover if not allowed", (done) => { 521 const perMessageDeflate = new PerMessageDeflate({}, true); 522 const extensions = extension.parse( 523 'permessage-deflate;server_no_context_takeover' 524 ); 525 const buf = Buffer.from('foofoo'); 526 527 perMessageDeflate.accept(extensions['permessage-deflate']); 528 529 perMessageDeflate.compress(buf, true, (err, compressed1) => { 530 if (err) return done(err); 531 532 perMessageDeflate.decompress(compressed1, true, (err, data) => { 533 if (err) return done(err); 534 535 assert.ok(data.equals(buf)); 536 perMessageDeflate.compress(data, true, (err, compressed2) => { 537 if (err) return done(err); 538 539 assert.strictEqual(compressed2.length, compressed1.length); 540 perMessageDeflate.decompress(compressed2, true, (err, data) => { 541 if (err) return done(err); 542 543 assert.ok(data.equals(buf)); 544 done(); 545 }); 546 }); 547 }); 548 }); 549 }); 550 551 it('uses contex takeover if allowed', (done) => { 552 const perMessageDeflate = new PerMessageDeflate({}, true); 553 const extensions = extension.parse('permessage-deflate'); 554 const buf = Buffer.from('foofoo'); 555 556 perMessageDeflate.accept(extensions['permessage-deflate']); 557 558 perMessageDeflate.compress(buf, true, (err, compressed1) => { 559 if (err) return done(err); 560 561 perMessageDeflate.decompress(compressed1, true, (err, data) => { 562 if (err) return done(err); 563 564 assert.ok(data.equals(buf)); 565 perMessageDeflate.compress(data, true, (err, compressed2) => { 566 if (err) return done(err); 567 568 assert.ok(compressed2.length < compressed1.length); 569 perMessageDeflate.decompress(compressed2, true, (err, data) => { 570 if (err) return done(err); 571 572 assert.ok(data.equals(buf)); 573 done(); 574 }); 575 }); 576 }); 577 }); 578 }); 579 580 it('calls the callback when an error occurs (inflate)', (done) => { 581 const perMessageDeflate = new PerMessageDeflate(); 582 const data = Buffer.from('something invalid'); 583 584 perMessageDeflate.accept([{}]); 585 perMessageDeflate.decompress(data, true, (err) => { 586 assert.ok(err instanceof Error); 587 assert.strictEqual(err.code, 'Z_DATA_ERROR'); 588 assert.strictEqual(err.errno, -3); 589 done(); 590 }); 591 }); 592 593 it("doesn't call the callback twice when `maxPayload` is exceeded", (done) => { 594 const perMessageDeflate = new PerMessageDeflate({}, false, 25); 595 const buf = Buffer.from('A'.repeat(50)); 596 597 perMessageDeflate.accept([{}]); 598 perMessageDeflate.compress(buf, true, (err, data) => { 599 if (err) return done(err); 600 601 perMessageDeflate.decompress(data, true, (err) => { 602 assert.ok(err instanceof RangeError); 603 assert.strictEqual(err.message, 'Max payload size exceeded'); 604 done(); 605 }); 606 }); 607 }); 608 609 it('calls the callback if the deflate stream is closed prematurely', (done) => { 610 const perMessageDeflate = new PerMessageDeflate(); 611 const buf = Buffer.from('A'.repeat(50)); 612 613 perMessageDeflate.accept([{}]); 614 perMessageDeflate.compress(buf, true, (err) => { 615 assert.ok(err instanceof Error); 616 assert.strictEqual( 617 err.message, 618 'The deflate stream was closed while data was being processed' 619 ); 620 done(); 621 }); 622 623 process.nextTick(() => perMessageDeflate.cleanup()); 624 }); 625 626 it('recreates the inflate stream if it ends', (done) => { 627 const perMessageDeflate = new PerMessageDeflate(); 628 const extensions = extension.parse( 629 'permessage-deflate; client_no_context_takeover; ' + 630 'server_no_context_takeover' 631 ); 632 const buf = Buffer.from('33343236313533b7000000', 'hex'); 633 const expected = Buffer.from('12345678'); 634 635 perMessageDeflate.accept(extensions['permessage-deflate']); 636 637 perMessageDeflate.decompress(buf, true, (err, data) => { 638 assert.ok(data.equals(expected)); 639 640 perMessageDeflate.decompress(buf, true, (err, data) => { 641 assert.ok(data.equals(expected)); 642 done(); 643 }); 644 }); 645 }); 646 }); 647 });