diff --git a/lib/Transport.js b/lib/Transport.js index b5bdcba53..6b0e17efc 100644 --- a/lib/Transport.js +++ b/lib/Transport.js @@ -22,6 +22,7 @@ const debug = require('debug')('elasticsearch') const os = require('os') const { gzip, unzip, createGzip } = require('zlib') +const buffer = require('buffer') const ms = require('ms') const { ConnectionError, @@ -35,6 +36,8 @@ const noop = () => {} const clientVersion = require('../package.json').version const userAgent = `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${process.version})` +const MAX_BUFFER_LENGTH = buffer.constants.MAX_LENGTH +const MAX_STRING_LENGTH = buffer.constants.MAX_STRING_LENGTH class Transport { constructor (opts) { @@ -218,6 +221,22 @@ class Transport { const contentEncoding = (result.headers['content-encoding'] || '').toLowerCase() const isCompressed = contentEncoding.indexOf('gzip') > -1 || contentEncoding.indexOf('deflate') > -1 + + /* istanbul ignore else */ + if (result.headers['content-length'] !== undefined) { + const contentLength = Number(result.headers['content-length']) + if (isCompressed && contentLength > MAX_BUFFER_LENGTH) { + response.destroy() + return onConnectionError( + new RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed buffer (${MAX_BUFFER_LENGTH})`, result) + ) + } else if (contentLength > MAX_STRING_LENGTH) { + response.destroy() + return onConnectionError( + new RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed string (${MAX_STRING_LENGTH})`, result) + ) + } + } // if the response is compressed, we must handle it // as buffer for allowing decompression later let payload = isCompressed ? [] : '' diff --git a/test/unit/client.test.js b/test/unit/client.test.js index 29993b2dc..4c3ae4924 100644 --- a/test/unit/client.test.js +++ b/test/unit/client.test.js @@ -21,7 +21,9 @@ const { test } = require('tap') const { URL } = require('url') -const { Client, ConnectionPool, Transport, errors } = require('../../index') +const buffer = require('buffer') +const intoStream = require('into-stream') +const { Client, ConnectionPool, Transport, Connection, errors } = require('../../index') const { CloudConnectionPool } = require('../../lib/pool') const { buildServer } = require('../utils') @@ -1243,3 +1245,58 @@ test('Socket destryed while reading the body', t => { }) }) }) + +test('Content length too big (buffer)', t => { + t.plan(4) + + class MockConnection extends Connection { + request (params, callback) { + const stream = intoStream(JSON.stringify({ hello: 'world' })) + stream.statusCode = 200 + stream.headers = { + 'content-type': 'application/json;utf=8', + 'content-encoding': 'gzip', + 'content-length': buffer.constants.MAX_LENGTH + 10, + connection: 'keep-alive', + date: new Date().toISOString() + } + stream.on('close', () => t.pass('Stream destroyed')) + process.nextTick(callback, null, stream) + return { abort () {} } + } + } + + const client = new Client({ node: 'http://localhost:9200', Connection: MockConnection }) + client.info((err, result) => { + t.ok(err instanceof errors.RequestAbortedError) + t.is(err.message, `The content length (${buffer.constants.MAX_LENGTH + 10}) is bigger than the maximum allowed buffer (${buffer.constants.MAX_LENGTH})`) + t.strictEqual(result.meta.attempts, 0) + }) +}) + +test('Content length too big (string)', t => { + t.plan(4) + + class MockConnection extends Connection { + request (params, callback) { + const stream = intoStream(JSON.stringify({ hello: 'world' })) + stream.statusCode = 200 + stream.headers = { + 'content-type': 'application/json;utf=8', + 'content-length': buffer.constants.MAX_STRING_LENGTH + 10, + connection: 'keep-alive', + date: new Date().toISOString() + } + stream.on('close', () => t.pass('Stream destroyed')) + process.nextTick(callback, null, stream) + return { abort () {} } + } + } + + const client = new Client({ node: 'http://localhost:9200', Connection: MockConnection }) + client.info((err, result) => { + t.ok(err instanceof errors.RequestAbortedError) + t.is(err.message, `The content length (${buffer.constants.MAX_STRING_LENGTH + 10}) is bigger than the maximum allowed string (${buffer.constants.MAX_STRING_LENGTH})`) + t.strictEqual(result.meta.attempts, 0) + }) +})