nodejs / help

:sparkles: Need help with Node.js? File an Issue here. :rocket:
1.47k stars 280 forks source link

Decompressing brotli causes error #4292

Closed ThePedroo closed 5 months ago

ThePedroo commented 10 months ago

Details

Hello,

I'm making a http(s) request function using tls/net. It works fine without "Accept-Encoding" header, but when it has, and server responds with brotlii (I haven't tried others yet), it throws this error:

node:zlib:467
      throw self[kError];
      ^

Error: Decompression failed
    at BrotliDecoder.zlibOnError [as onerror] (node:zlib:189:17)
    at processChunkSync (node:zlib:457:12)
    at zlibBufferSync (node:zlib:178:12)
    at Object.syncBufferWrapper [as brotliDecompressSync] (node:zlib:792:14)
    at TLSSocket.<anonymous> (file:///home/system/Downloads/Projects/Nodelink/src/utils.js:116:25)
    at TLSSocket.emit (node:events:515:28)
    at addChunk (node:internal/streams/readable:545:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:495:3)
    at Readable.push (node:internal/streams/readable:375:5) {
  errno: -12,
  code: 'ERR_DICTIONARY'
}

Node.js version

v21.1.0

Example code

export function newHttp1MakeRequest(url, options) {
  return new Promise(async (resolve, reject) => {
    const parsedUrl = new URL(url)

    let socket = (parsedUrl.protocol == 'https:' ? tls : net).connect({
      host: parsedUrl.hostname,
      port: parsedUrl.port || (parsedUrl.protocol == 'https:' ? 443 : 80),
      rejectUnauthorized: false,
      ALPNProtocols: [
        'http/1.1'
      ],
      servername: parsedUrl.hostname,
      highWaterMark: 1024
    }, () => {
      const headers = [
        `${options.method} ${parsedUrl.pathname + parsedUrl.search} HTTP/1.1`,
        `Host: ${parsedUrl.hostname}`,
        'Accept-Encoding: br, gzip, deflate',
        'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0',
        ...(options.headers ? Object.entries(options.headers).map(([ key, value ]) => `${key}: ${value}`) : [])
      ]

      socket.write(headers.join('\r\n') + '\r\n\r\n')
    })

    socket.on('error', (error) => {
      console.error(`[\u001b[31mhttp1makeRequest\u001b[37m]: Failed sending HTTP request to ${url}: \u001b[31m${error}\u001b[37m`)

      reject(error)
    })

    let buffer = []
    let headers = null

    socket.on('data', (data) => {
      const separator1 = Buffer.from('\r\n\r\n')
      const separator2 = Buffer.from('\r\n')

      if (!headers) {
        const tmpHeaders = data.toString().split('\r\n\r\n')[0].split('\r\n').slice(1).map((e) => e.split(': ')).reduce((acc, [ key, value ]) => ({ ...acc, [key]: value }), {})

        if (tmpHeaders && Object.keys(tmpHeaders).length != 0) headers = tmpHeaders
      }

      let body = data.subarray(data.indexOf(separator1) + separator1.length)
      let length = body.indexOf(separator2)
      body = body.subarray(length + separator2.length)

      if (body.length > 0) buffer.push(body)

      if (Buffer.from([ 0x0d, 0x0a, 0x30, 0x0d, 0x0a, 0x0d, 0x0a ]).equals(data.subarray(-7))) {
        buffer.push(data.subarray(0, data.length - 7))

        let body = Buffer.concat(buffer)

        switch (headers['Content-Encoding']) {
          case 'deflate': {
            body = zlib.inflateSync(body)
            break
          }
          case 'br': {
            body = zlib.brotliDecompressSync(body)
            break
          }
          case 'gzip': {
            body = zlib.gunzipSync(body)
            break
          }
        }

        resolve(headers['Content-Type'].startsWith('application/json') ? JSON.parse(body.toString()) : body.toString())

        socket.end()
      }
    })
  })
}

Operating system

Ubuntu 23.04

Scope

code

Module and version

Not applicable.

preveen-stack commented 10 months ago

check this example

const https = require('https');
const zlib = require('zlib');

const options = {
  hostname: 'example.com',
  path: '/path/to/resource',
  method: 'GET',
  headers: {
    'Accept-Encoding': 'br' // Request Brotli encoding
  }
};

const req = https.request(options, (res) => {
  let chunks = [];

  // Handle the response data
  res.on('data', (chunk) => {
    chunks.push(chunk);
  });

  // Handle the end of the response
  res.on('end', () => {
    const body = Buffer.concat(chunks);

    // Check if Brotli encoding is used
    if (res.headers['content-encoding'] === 'br') {
      // Decompress the Brotli-encoded response
      zlib.brotliDecompress(body, (err, decompressed) => {
        if (err) {
          console.error('Error decompressing Brotli response:', err);
        } else {
          console.log('Decompressed response:', decompressed.toString());
        }
      });
    } else {
      // Response is not Brotli-encoded, handle as needed
      console.log('Response:', body.toString());
    }
  });
});

// Handle errors
req.on('error', (e) => {
  console.error(`Error making the request: ${e.message}`);
});

// Send the request
req.end();
ThePedroo commented 10 months ago

Thanks before anything. Yes, I'm aware of how it works with http(s) libs, the issue is that when I try using it with tls/net, which is my goal, it doesn't work